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:
Diffstat (limited to 'spec/requests')
-rw-r--r--spec/requests/acme_challenges_controller_spec.rb9
-rw-r--r--spec/requests/admin/users_controller_spec.rb50
-rw-r--r--spec/requests/api/badges_spec.rb2
-rw-r--r--spec/requests/api/bulk_imports_spec.rb15
-rw-r--r--spec/requests/api/ci/job_artifacts_spec.rb70
-rw-r--r--spec/requests/api/ci/jobs_spec.rb26
-rw-r--r--spec/requests/api/ci/pipelines_spec.rb154
-rw-r--r--spec/requests/api/ci/resource_groups_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/jobs_artifacts_spec.rb11
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb64
-rw-r--r--spec/requests/api/ci/runner/jobs_trace_spec.rb22
-rw-r--r--spec/requests/api/ci/runner/runners_post_spec.rb8
-rw-r--r--spec/requests/api/ci/runners_spec.rb51
-rw-r--r--spec/requests/api/ci/triggers_spec.rb2
-rw-r--r--spec/requests/api/commits_spec.rb10
-rw-r--r--spec/requests/api/container_repositories_spec.rb4
-rw-r--r--spec/requests/api/deployments_spec.rb2
-rw-r--r--spec/requests/api/geo_spec.rb2
-rw-r--r--spec/requests/api/graphql/abuse_report_spec.rb131
-rw-r--r--spec/requests/api/graphql/ci/catalog/resource_spec.rb341
-rw-r--r--spec/requests/api/graphql/ci/catalog/resources_spec.rb359
-rw-r--r--spec/requests/api/graphql/ci/manual_variables_spec.rb2
-rw-r--r--spec/requests/api/graphql/ci/runners_spec.rb87
-rw-r--r--spec/requests/api/graphql/container_repository/container_repository_details_spec.rb120
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb6
-rw-r--r--spec/requests/api/graphql/group/container_repositories_spec.rb4
-rw-r--r--spec/requests/api/graphql/group/data_transfer_spec.rb42
-rw-r--r--spec/requests/api/graphql/group/milestones_spec.rb8
-rw-r--r--spec/requests/api/graphql/merge_requests/codequality_reports_comparer_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb12
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb32
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb37
-rw-r--r--spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb34
-rw-r--r--spec/requests/api/graphql/mutations/ci/catalog/resources/create_spec.rb40
-rw-r--r--spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb16
-rw-r--r--spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb180
-rw-r--r--spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/design_management/delete_spec.rb10
-rw-r--r--spec/requests/api/graphql/mutations/design_management/upload_spec.rb9
-rw-r--r--spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb28
-rw-r--r--spec/requests/api/graphql/mutations/issues/move_spec.rb18
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb20
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb26
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb22
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_locked_spec.rb20
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_severity_spec.rb20
-rw-r--r--spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb28
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb12
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb28
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb20
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb28
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb20
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb24
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb28
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_time_estimate_spec.rb5
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/update_spec.rb17
-rw-r--r--spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb20
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb43
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb12
-rw-r--r--spec/requests/api/graphql/mutations/organizations/create_spec.rb64
-rw-r--r--spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb18
-rw-r--r--spec/requests/api/graphql/mutations/packages/protection/rule/create_spec.rb232
-rw-r--r--spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb88
-rw-r--r--spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/snippets/create_spec.rb10
-rw-r--r--spec/requests/api/graphql/mutations/snippets/destroy_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/snippets/update_spec.rb38
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_done_spec.rb20
-rw-r--r--spec/requests/api/graphql/mutations/todos/restore_many_spec.rb20
-rw-r--r--spec/requests/api/graphql/mutations/todos/restore_spec.rb20
-rw-r--r--spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb9
-rw-r--r--spec/requests/api/graphql/organizations/organization_query_spec.rb7
-rw-r--r--spec/requests/api/graphql/project/base_service_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/container_repositories_spec.rb14
-rw-r--r--spec/requests/api/graphql/project/data_transfer_spec.rb42
-rw-r--r--spec/requests/api/graphql/project/environments_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/jira_import_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/jira_projects_spec.rb8
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb5
-rw-r--r--spec/requests/api/graphql/project/release_spec.rb24
-rw-r--r--spec/requests/api/graphql/project/terraform/state_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/terraform/states_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/work_items_spec.rb17
-rw-r--r--spec/requests/api/graphql/projects/projects_spec.rb77
-rw-r--r--spec/requests/api/graphql/user_spec.rb32
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb75
-rw-r--r--spec/requests/api/group_packages_spec.rb23
-rw-r--r--spec/requests/api/groups_spec.rb38
-rw-r--r--spec/requests/api/helm_packages_spec.rb6
-rw-r--r--spec/requests/api/internal/base_spec.rb50
-rw-r--r--spec/requests/api/internal/pages_spec.rb42
-rw-r--r--spec/requests/api/issues/get_group_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/issues_spec.rb4
-rw-r--r--spec/requests/api/issues/post_projects_issues_spec.rb8
-rw-r--r--spec/requests/api/issues/put_projects_issues_spec.rb2
-rw-r--r--spec/requests/api/maven_packages_spec.rb32
-rw-r--r--spec/requests/api/members_spec.rb48
-rw-r--r--spec/requests/api/merge_request_approvals_spec.rb24
-rw-r--r--spec/requests/api/merge_requests_spec.rb8
-rw-r--r--spec/requests/api/metadata_spec.rb8
-rw-r--r--spec/requests/api/ml/mlflow/model_versions_spec.rb86
-rw-r--r--spec/requests/api/ml/mlflow/registered_models_spec.rb203
-rw-r--r--spec/requests/api/npm_project_packages_spec.rb4
-rw-r--r--spec/requests/api/pages/pages_spec.rb13
-rw-r--r--spec/requests/api/personal_access_tokens_spec.rb12
-rw-r--r--spec/requests/api/project_attributes.yml1
-rw-r--r--spec/requests/api/project_container_repositories_spec.rb10
-rw-r--r--spec/requests/api/project_packages_spec.rb20
-rw-r--r--spec/requests/api/project_repository_storage_moves_spec.rb24
-rw-r--r--spec/requests/api/project_templates_spec.rb4
-rw-r--r--spec/requests/api/projects_spec.rb67
-rw-r--r--spec/requests/api/pypi_packages_spec.rb17
-rw-r--r--spec/requests/api/releases_spec.rb2
-rw-r--r--spec/requests/api/repositories_spec.rb6
-rw-r--r--spec/requests/api/resource_access_tokens_spec.rb16
-rw-r--r--spec/requests/api/search_spec.rb4
-rw-r--r--spec/requests/api/settings_spec.rb10
-rw-r--r--spec/requests/api/tags_spec.rb2
-rw-r--r--spec/requests/api/task_completion_status_spec.rb16
-rw-r--r--spec/requests/api/unleash_spec.rb2
-rw-r--r--spec/requests/api/users_spec.rb44
-rw-r--r--spec/requests/api/v3/github_spec.rb721
-rw-r--r--spec/requests/api/vs_code/settings/vs_code_settings_sync_spec.rb89
-rw-r--r--spec/requests/api/wikis_spec.rb30
-rw-r--r--spec/requests/application_controller_spec.rb15
-rw-r--r--spec/requests/chaos_controller_spec.rb14
-rw-r--r--spec/requests/explore/catalog_controller_spec.rb53
-rw-r--r--spec/requests/external_redirect/external_redirect_controller_spec.rb60
-rw-r--r--spec/requests/health_controller_spec.rb4
-rw-r--r--spec/requests/jira_authorizations_spec.rb88
-rw-r--r--spec/requests/jwt_controller_spec.rb55
-rw-r--r--spec/requests/lfs_http_spec.rb6
-rw-r--r--spec/requests/lfs_locks_api_spec.rb8
-rw-r--r--spec/requests/metrics_controller_spec.rb9
-rw-r--r--spec/requests/oauth/authorizations_controller_spec.rb4
-rw-r--r--spec/requests/organizations/organizations_controller_spec.rb6
-rw-r--r--spec/requests/profiles/comment_templates_controller_spec.rb22
-rw-r--r--spec/requests/projects/merge_requests_controller_spec.rb15
-rw-r--r--spec/requests/projects/ml/model_versions_controller_spec.rb72
-rw-r--r--spec/requests/projects/ml/models_controller_spec.rb81
-rw-r--r--spec/requests/projects/service_desk_controller_spec.rb10
-rw-r--r--spec/requests/registrations_controller_spec.rb6
-rw-r--r--spec/requests/search_controller_spec.rb1
-rw-r--r--spec/requests/sessions_spec.rb4
-rw-r--r--spec/requests/users_controller_spec.rb4
155 files changed, 3690 insertions, 1904 deletions
diff --git a/spec/requests/acme_challenges_controller_spec.rb b/spec/requests/acme_challenges_controller_spec.rb
deleted file mode 100644
index f37aefed488..00000000000
--- a/spec/requests/acme_challenges_controller_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe AcmeChallengesController, type: :request, feature_category: :pages do
- it_behaves_like 'Base action controller' do
- subject(:request) { get acme_challenge_path }
- end
-end
diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb
index e525d615b50..2f8025691f4 100644
--- a/spec/requests/admin/users_controller_spec.rb
+++ b/spec/requests/admin/users_controller_spec.rb
@@ -74,4 +74,54 @@ RSpec.describe Admin::UsersController, :enable_admin_mode, feature_category: :us
expect { request }.to change { user.reload.access_locked? }.from(true).to(false)
end
end
+
+ describe 'PUT #trust' do
+ subject(:request) { put trust_admin_user_path(user) }
+
+ it 'trusts the user' do
+ expect { request }.to change { user.reload.trusted? }.from(false).to(true)
+ end
+
+ context 'when setting trust fails' do
+ before do
+ allow_next_instance_of(Users::TrustService) do |instance|
+ allow(instance).to receive(:execute).and_return({ status: :failed })
+ end
+ end
+
+ it 'displays a flash alert' do
+ request
+
+ expect(response).to redirect_to(admin_user_path(user))
+ expect(flash[:alert]).to eq(s_('Error occurred. User was not updated'))
+ end
+ end
+ end
+
+ describe 'PUT #untrust' do
+ before do
+ user.custom_attributes.create!(key: UserCustomAttribute::TRUSTED_BY, value: "placeholder")
+ end
+
+ subject(:request) { put untrust_admin_user_path(user) }
+
+ it 'trusts the user' do
+ expect { request }.to change { user.reload.trusted? }.from(true).to(false)
+ end
+
+ context 'when untrusting fails' do
+ before do
+ allow_next_instance_of(Users::UntrustService) do |instance|
+ allow(instance).to receive(:execute).and_return({ status: :failed })
+ end
+ end
+
+ it 'displays a flash alert' do
+ request
+
+ expect(response).to redirect_to(admin_user_path(user))
+ expect(flash[:alert]).to eq(s_('Error occurred. User was not updated'))
+ end
+ end
+ end
end
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
index 0b340b95b20..92c5e90027b 100644
--- a/spec/requests/api/badges_spec.rb
+++ b/spec/requests/api/badges_spec.rb
@@ -363,7 +363,7 @@ RSpec.describe API::Badges, feature_category: :groups_and_projects do
end
describe 'Endpoints' do
- %w(project group).each do |source_type|
+ %w[project group].each do |source_type|
it_behaves_like 'GET /:sources/:id/badges', source_type
it_behaves_like 'GET /:sources/:id/badges/:badge_id', source_type
it_behaves_like 'GET /:sources/:id/badges/render', source_type
diff --git a/spec/requests/api/bulk_imports_spec.rb b/spec/requests/api/bulk_imports_spec.rb
index d3d4a723616..bbc01b30361 100644
--- a/spec/requests/api/bulk_imports_spec.rb
+++ b/spec/requests/api/bulk_imports_spec.rb
@@ -394,7 +394,7 @@ RSpec.describe API::BulkImports, feature_category: :importers do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.pluck('id')).to contain_exactly(entity_3.id)
- expect(json_response.first['failures'].first['exception_class']).to eq(failure_3.exception_class)
+ expect(json_response.first['failures'].first['exception_message']).to eq(failure_3.exception_message)
end
it_behaves_like 'disabled feature'
@@ -420,4 +420,17 @@ RSpec.describe API::BulkImports, feature_category: :importers do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
+
+ describe 'GET /bulk_imports/:id/entities/:entity_id/failures' do
+ let(:request) { get api("/bulk_imports/#{import_2.id}/entities/#{entity_3.id}/failures", user) }
+
+ it 'returns specified entity failures' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first['exception_message']).to eq(failure_3.exception_message)
+ end
+
+ it_behaves_like 'disabled feature'
+ end
end
diff --git a/spec/requests/api/ci/job_artifacts_spec.rb b/spec/requests/api/ci/job_artifacts_spec.rb
index 6f4e7fd66ed..b96ba356855 100644
--- a/spec/requests/api/ci/job_artifacts_spec.rb
+++ b/spec/requests/api/ci/job_artifacts_spec.rb
@@ -14,9 +14,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
end
let_it_be(:pipeline, reload: true) do
- create(:ci_pipeline, project: project,
- sha: project.commit.id,
- ref: project.default_branch)
+ create(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
end
let(:user) { create(:user) }
@@ -179,8 +177,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
context 'when project is public' do
it 'allows to access artifacts' do
- project.update_column(:visibility_level,
- Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
project.update_column(:public_builds, true)
get_artifact_file(artifact)
@@ -193,8 +190,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
let(:job) { create(:ci_build, :artifacts, :with_private_artifacts_config, pipeline: pipeline) }
it 'rejects access to artifacts' do
- project.update_column(:visibility_level,
- Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
project.update_column(:public_builds, true)
get_artifact_file(artifact)
@@ -208,8 +204,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
end
it 'allows access to artifacts' do
- project.update_column(:visibility_level,
- Gitlab::VisibilityLevel::PUBLIC)
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
project.update_column(:public_builds, true)
get_artifact_file(artifact)
@@ -221,8 +216,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
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(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
project.update_column(:public_builds, false)
get_artifact_file(artifact)
@@ -233,8 +227,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
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(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
project.update_column(:public_builds, true)
get_artifact_file(artifact)
@@ -254,8 +247,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts 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/)
+ .to include('Content-Type' => 'application/json', 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
expect(response.headers.to_h)
.not_to include('Gitlab-Workhorse-Detect-Content-Type' => 'true')
expect(response.parsed_body).to be_empty
@@ -404,10 +396,12 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
end
before do
- stub_object_storage_uploader(config: Gitlab.config.artifacts.object_store,
- uploader: JobArtifactUploader,
- proxy_download: proxy_download,
- cdn: cdn_config)
+ stub_object_storage_uploader(
+ config: Gitlab.config.artifacts.object_store,
+ uploader: JobArtifactUploader,
+ proxy_download: proxy_download,
+ cdn: cdn_config
+ )
allow(Gitlab::ApplicationContext).to receive(:push).and_call_original
end
@@ -624,10 +618,11 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
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/,
- 'Gitlab-Workhorse-Detect-Content-Type' => 'true')
+ expect(response.headers.to_h).to include(
+ 'Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/,
+ 'Gitlab-Workhorse-Detect-Content-Type' => 'true'
+ )
end
end
@@ -695,10 +690,11 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
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/,
- 'Gitlab-Workhorse-Detect-Content-Type' => 'true')
+ expect(response.headers.to_h).to include(
+ 'Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/,
+ 'Gitlab-Workhorse-Detect-Content-Type' => 'true'
+ )
expect(response.parsed_body).to be_empty
end
end
@@ -713,10 +709,11 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts 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/,
- 'Gitlab-Workhorse-Detect-Content-Type' => 'true')
+ expect(response.headers.to_h).to include(
+ 'Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/,
+ 'Gitlab-Workhorse-Detect-Content-Type' => 'true'
+ )
end
end
@@ -765,8 +762,15 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do
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)
+ create(
+ :ci_build,
+ :trace_artifact,
+ :artifacts,
+ :success,
+ project: project,
+ pipeline: pipeline,
+ artifacts_expire_at: Time.now + 7.days
+ )
end
it 'keeps artifacts' do
diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb
index 41e35de189e..382aabd45a1 100644
--- a/spec/requests/api/ci/jobs_spec.rb
+++ b/spec/requests/api/ci/jobs_spec.rb
@@ -14,9 +14,7 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
end
let_it_be(:pipeline, reload: true) do
- create(:ci_pipeline, project: project,
- sha: project.commit.id,
- ref: project.default_branch)
+ create(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
end
let(:user) { create(:user) }
@@ -25,10 +23,14 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
let(:guest) { create(:project_member, :guest, project: project).user }
let(:running_job) do
- create(:ci_build, :running, project: project,
- user: user,
- pipeline: pipeline,
- artifacts_expire_at: 1.day.since)
+ create(
+ :ci_build,
+ :running,
+ project: project,
+ user: user,
+ pipeline: pipeline,
+ artifacts_expire_at: 1.day.since
+ )
end
let!(:job) do
@@ -266,7 +268,7 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
expect(json_response.dig('project', 'groups')).to match_array([{ 'id' => group.id }])
expect(json_response.dig('user', 'id')).to eq(api_user.id)
expect(json_response.dig('user', 'username')).to eq(api_user.username)
- expect(json_response.dig('user', 'roles_in_project')).to match_array %w(guest reporter developer)
+ expect(json_response.dig('user', 'roles_in_project')).to match_array %w[guest reporter developer]
expect(json_response).not_to include('environment')
end
@@ -450,7 +452,7 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
end
context 'filter project with array of scope elements' do
- let(:query) { { scope: %w(pending running) } }
+ let(:query) { { scope: %w[pending running] } }
it do
expect(response).to have_gitlab_http_status(:ok)
@@ -459,7 +461,7 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
end
context 'respond 400 when scope contains invalid state' do
- let(:query) { { scope: %w(unknown running) } }
+ let(:query) { { scope: %w[unknown running] } }
it { expect(response).to have_gitlab_http_status(:bad_request) }
end
@@ -789,14 +791,14 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
end
context 'authorized user' do
- context 'user with :update_build persmission' do
+ context 'user with :cancel_build permission' do
it 'cancels running or pending job' do
expect(response).to have_gitlab_http_status(:created)
expect(project.builds.first.status).to eq('success')
end
end
- context 'user without :update_build permission' do
+ context 'user without :cancel_build permission' do
let(:api_user) { reporter }
it 'does not cancel job' do
diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb
index 3544a6dd72a..eef125e1bc3 100644
--- a/spec/requests/api/ci/pipelines_spec.rb
+++ b/spec/requests/api/ci/pipelines_spec.rb
@@ -13,8 +13,14 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
end
let_it_be(:pipeline) do
- create(:ci_empty_pipeline, project: project, sha: project.commit.id,
- ref: project.default_branch, user: user, name: 'Build pipeline')
+ create(
+ :ci_empty_pipeline,
+ project: project,
+ sha: project.commit.id,
+ ref: project.default_branch,
+ user: user,
+ name: 'Build pipeline'
+ )
end
before do
@@ -357,8 +363,13 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
let(:query) { {} }
let(:api_user) { user }
let_it_be(:job) do
- create(:ci_build, :success, name: 'build', pipeline: pipeline,
- artifacts_expire_at: 1.day.since)
+ create(
+ :ci_build,
+ :success,
+ name: 'build',
+ pipeline: pipeline,
+ artifacts_expire_at: 1.day.since
+ )
end
let(:guest) { create(:project_member, :guest, project: project).user }
@@ -436,7 +447,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
end
context 'filter jobs with array of scope elements' do
- let(:query) { { scope: %w(pending running) } }
+ let(:query) { { scope: %w[pending running] } }
it :aggregate_failures do
expect(response).to have_gitlab_http_status(:ok)
@@ -445,7 +456,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
end
context 'respond 400 when scope contains invalid state' do
- let(:query) { { scope: %w(unknown running) } }
+ let(:query) { { scope: %w[unknown running] } }
it { expect(response).to have_gitlab_http_status(:bad_request) }
end
@@ -540,12 +551,14 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
let(:downstream_pipeline) { create(:ci_pipeline) }
let!(:pipeline_source) do
- create(:ci_sources_pipeline,
- source_pipeline: pipeline,
- source_project: project,
- source_job: bridge,
- pipeline: downstream_pipeline,
- project: downstream_pipeline.project)
+ create(
+ :ci_sources_pipeline,
+ source_pipeline: pipeline,
+ source_project: project,
+ source_job: bridge,
+ pipeline: downstream_pipeline,
+ project: downstream_pipeline.project
+ )
end
let(:query) { {} }
@@ -615,7 +628,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
end
context 'with array of scope elements' do
- let(:query) { { scope: %w(pending running) } }
+ let(:query) { { scope: %w[pending running] } }
it :skip_before_request, :aggregate_failures do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}/bridges", api_user), params: query
@@ -623,14 +636,14 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an Array
expect(json_response.count).to eq 2
- json_response.each { |r| expect(%w(pending running).include?(r['status'])).to be true }
+ json_response.each { |r| expect(%w[pending running].include?(r['status'])).to be true }
end
end
end
context 'respond 400 when scope contains invalid state' do
context 'in an array' do
- let(:query) { { scope: %w(unknown running) } }
+ let(:query) { { scope: %w[unknown running] } }
it { expect(response).to have_gitlab_http_status(:bad_request) }
end
@@ -713,12 +726,14 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
def create_bridge(pipeline, status = :created)
create(:ci_bridge, status: status, pipeline: pipeline).tap do |bridge|
downstream_pipeline = create(:ci_pipeline)
- create(:ci_sources_pipeline,
- source_pipeline: pipeline,
- source_project: pipeline.project,
- source_job: bridge,
- pipeline: downstream_pipeline,
- project: downstream_pipeline.project)
+ create(
+ :ci_sources_pipeline,
+ source_pipeline: pipeline,
+ source_project: pipeline.project,
+ source_job: bridge,
+ pipeline: downstream_pipeline,
+ project: downstream_pipeline.project
+ )
end
end
end
@@ -914,13 +929,24 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
let(:second_branch) { project.repository.branches[2] }
let!(:second_pipeline) do
- create(:ci_empty_pipeline, project: project, sha: second_branch.target,
- ref: second_branch.name, user: user, name: 'Build pipeline')
+ create(
+ :ci_empty_pipeline,
+ project: project,
+ sha: second_branch.target,
+ ref: second_branch.name,
+ user: user,
+ name: 'Build pipeline'
+ )
end
before do
- create(:ci_empty_pipeline, project: project, sha: project.commit.parent.id,
- ref: project.default_branch, user: user)
+ create(
+ :ci_empty_pipeline,
+ project: project,
+ sha: project.commit.parent.id,
+ ref: project.default_branch,
+ user: user
+ )
end
context 'default repository branch' do
@@ -1107,11 +1133,82 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
end
end
+ describe 'PUT /projects/:id/pipelines/:pipeline_id/name' do
+ let_it_be(:pipeline_creator) { create(:user) }
+ let(:pipeline) { create(:ci_pipeline, project: project, user: pipeline_creator) }
+ let(:name) { 'A new pipeline name' }
+
+ subject(:execute) do
+ put api("/projects/#{project.id}/pipelines/#{pipeline.id}/metadata", current_user), params: { name: name }
+ end
+
+ context 'authorized user' do
+ let(:current_user) { create(:user) }
+
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'renames pipeline when name is valid', :aggregate_failures do
+ expect { execute }.to change { pipeline.reload.name }.to(name)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'when name is invalid' do
+ let(:name) { 'a' * 256 }
+
+ it 'does not rename pipeline', :aggregate_failures do
+ expect { execute }.not_to change { pipeline.reload.name }
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq('Failed to update pipeline - Name is too long (maximum is 255 characters)')
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:current_user) { create(:user) }
+
+ context 'when user is not a member' do
+ it 'does not rename pipeline', :aggregate_failures do
+ expect { execute }.not_to change { pipeline.reload.name }
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when user is a member' do
+ before do
+ project.add_reporter(current_user)
+ end
+
+ it 'does not rename pipeline', :aggregate_failures do
+ expect { execute }.not_to change { pipeline.reload.name }
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'when authorized with job token' do
+ let(:job) { create(:ci_build, :running, pipeline: pipeline, project: project, user: pipeline.user) }
+
+ before do
+ project.add_developer(pipeline.user)
+ end
+
+ subject(:execute) do
+ put api("/projects/#{project.id}/pipelines/#{pipeline.id}/metadata", nil, job_token: job.token), params: { name: name }
+ end
+
+ it 'renames pipeline when name is valid', :aggregate_failures do
+ expect { execute }.to change { pipeline.reload.name }.to(name)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
describe 'POST /projects/:id/pipelines/:pipeline_id/retry' do
context 'authorized user' do
let_it_be(:pipeline) do
- create(:ci_pipeline, project: project, sha: project.commit.id,
- ref: project.default_branch)
+ create(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
end
let_it_be(:build) { create(:ci_build, :failed, pipeline: pipeline) }
@@ -1156,8 +1253,7 @@ RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do
describe 'POST /projects/:id/pipelines/:pipeline_id/cancel' do
let_it_be(:pipeline) do
- create(:ci_empty_pipeline, project: project, sha: project.commit.id,
- ref: project.default_branch)
+ create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch)
end
let_it_be(:build) { create(:ci_build, :running, pipeline: pipeline) }
diff --git a/spec/requests/api/ci/resource_groups_spec.rb b/spec/requests/api/ci/resource_groups_spec.rb
index 26265aec1dc..809b1c7c3d3 100644
--- a/spec/requests/api/ci/resource_groups_spec.rb
+++ b/spec/requests/api/ci/resource_groups_spec.rb
@@ -126,9 +126,7 @@ RSpec.describe API::Ci::ResourceGroups, feature_category: :continuous_delivery d
context 'when resource group key contains a slash' do
let_it_be(:resource_group) { create(:ci_resource_group, project: project, key: 'test/test') }
let_it_be(:upcoming_processable) do
- create(:ci_processable,
- :waiting_for_resource,
- resource_group: resource_group)
+ create(:ci_processable, :waiting_for_resource, resource_group: resource_group)
end
let(:key) { 'test%2Ftest' }
diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
index 2e0be23ba90..637469411d5 100644
--- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
@@ -30,8 +30,15 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
describe '/api/v4/jobs' do
let(:job) do
- create(:ci_build, :artifacts, :extended_options,
- pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
+ create(
+ :ci_build,
+ :artifacts,
+ :extended_options,
+ pipeline: pipeline,
+ name: 'spinach',
+ stage: 'test',
+ stage_idx: 0
+ )
end
describe '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 7f9c9a13311..2a870a25ea6 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -24,8 +24,17 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master') }
let(:job) do
- create(:ci_build, :pending, :queued, :artifacts, :extended_options,
- pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
+ create(
+ :ci_build,
+ :pending,
+ :queued,
+ :artifacts,
+ :extended_options,
+ pipeline: pipeline,
+ name: 'spinach',
+ stage: 'test',
+ stage_idx: 0
+ )
end
describe 'POST /api/v4/jobs/request' do
@@ -202,12 +211,12 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
let(:expected_steps) do
[{ 'name' => 'script',
- 'script' => %w(echo),
+ 'script' => %w[echo],
'timeout' => job.metadata_timeout,
'when' => 'on_success',
'allow_failure' => false },
{ 'name' => 'after_script',
- 'script' => %w(ls date),
+ 'script' => %w[ls date],
'timeout' => job.metadata_timeout,
'when' => 'always',
'allow_failure' => true }]
@@ -226,7 +235,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
let(:expected_artifacts) do
[{ 'name' => 'artifacts_file',
'untracked' => false,
- 'paths' => %w(out/),
+ 'paths' => %w[out/],
'when' => 'always',
'expire_in' => '7d',
"artifact_type" => "archive",
@@ -342,10 +351,11 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
request_job
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['refspecs'])
- .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
- '+refs/tags/*:refs/tags/*',
- '+refs/heads/*:refs/remotes/origin/*')
+ expect(json_response['git_info']['refspecs']).to contain_exactly(
+ "+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
+ '+refs/tags/*:refs/tags/*',
+ '+refs/heads/*:refs/remotes/origin/*'
+ )
end
end
end
@@ -383,10 +393,11 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
request_job
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['git_info']['refspecs'])
- .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
- '+refs/tags/*:refs/tags/*',
- '+refs/heads/*:refs/remotes/origin/*')
+ expect(json_response['git_info']['refspecs']).to contain_exactly(
+ "+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
+ '+refs/tags/*:refs/tags/*',
+ '+refs/heads/*:refs/remotes/origin/*'
+ )
end
end
end
@@ -461,7 +472,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
expect { request_job }.to change { runner.reload.contacted_at }
end
- %w(version revision platform architecture).each do |param|
+ %w[version revision platform architecture].each do |param|
context "when info parameter '#{param}' is present" do
let(:value) { "#{param}_value" }
@@ -646,8 +657,16 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
context 'when job has code coverage report' do
let(:job) do
- create(:ci_build, :pending, :queued, :coverage_report_cobertura,
- pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
+ create(
+ :ci_build,
+ :pending,
+ :queued,
+ :coverage_report_cobertura,
+ pipeline: pipeline,
+ name: 'spinach',
+ stage: 'test',
+ stage_idx: 0
+ )
end
let(:expected_artifacts) do
@@ -788,9 +807,16 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
describe 'time_in_queue_seconds support' do
let(:job) do
- create(:ci_build, :pending, :queued, pipeline: pipeline,
- name: 'spinach', stage: 'test', stage_idx: 0,
- queued_at: 60.seconds.ago)
+ create(
+ :ci_build,
+ :pending,
+ :queued,
+ pipeline: pipeline,
+ name: 'spinach',
+ stage: 'test',
+ stage_idx: 0,
+ queued_at: 60.seconds.ago
+ )
end
it 'presents the time_in_queue_seconds info in the payload' do
diff --git a/spec/requests/api/ci/runner/jobs_trace_spec.rb b/spec/requests/api/ci/runner/jobs_trace_spec.rb
index ee00fc5a793..8c596d2338f 100644
--- a/spec/requests/api/ci/runner/jobs_trace_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_trace_spec.rb
@@ -23,14 +23,28 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks, feature_catego
let(:runner) { create(:ci_runner, :project, projects: [project]) }
let(:user) { create(:user) }
let(:job) do
- create(:ci_build, :artifacts, :extended_options,
- pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
+ create(
+ :ci_build,
+ :artifacts,
+ :extended_options,
+ pipeline: pipeline,
+ name: 'spinach',
+ stage: 'test',
+ stage_idx: 0
+ )
end
describe 'PATCH /api/v4/jobs/:id/trace' do
let(:job) do
- create(:ci_build, :running, :trace_live,
- project: project, user: user, runner_id: runner.id, pipeline: pipeline)
+ create(
+ :ci_build,
+ :running,
+ :trace_live,
+ project: project,
+ user: user,
+ runner_id: runner.id,
+ pipeline: pipeline
+ )
end
let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } }
diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb
index c5e49e9ac54..1490172d1c3 100644
--- a/spec/requests/api/ci/runner/runners_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_post_spec.rb
@@ -48,7 +48,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
description: 'server.hostname',
maintenance_note: 'Some maintainer notes',
run_untagged: false,
- tag_list: %w(tag1 tag2),
+ tag_list: %w[tag1 tag2],
locked: true,
active: true,
access_level: 'ref_protected',
@@ -167,7 +167,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
end
- %w(name version revision platform architecture).each do |param|
+ %w[name version revision platform architecture].each do |param|
context "when info parameter '#{param}' info is present" do
let(:value) { "#{param}_value" }
@@ -185,8 +185,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
it "sets the runner's ip_address" do
post api('/runners'),
- params: { token: registration_token },
- headers: { 'X-Forwarded-For' => '123.111.123.111' }
+ params: { token: registration_token },
+ headers: { 'X-Forwarded-For' => '123.111.123.111' }
expect(response).to have_gitlab_http_status(:created)
expect(::Ci::Runner.last.ip_address).to eq('123.111.123.111')
diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index 2b2d2e0def8..ba80684e89e 100644
--- a/spec/requests/api/ci/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -249,6 +249,39 @@ RSpec.describe API::Ci::Runners, :aggregate_failures, feature_category: :runner_
a_hash_including('description' => 'Runner tagged with tag1 and tag2')
]
end
+
+ context 'with ci_runner_machines' do
+ let_it_be(:version_ci_runner) { create(:ci_runner, :project, description: 'Runner with machine') }
+ let_it_be(:version_ci_runner_machine) { create(:ci_runner_machine, runner: version_ci_runner, version: '15.0.3') }
+ let_it_be(:version_16_ci_runner) { create(:ci_runner, :project, description: 'Runner with machine version 16') }
+ let_it_be(:version_16_ci_runner_machine) { create(:ci_runner_machine, runner: version_16_ci_runner, version: '16.0.1') }
+
+ it 'filters runners by version_prefix when prefix is "15.0"' do
+ get api('/runners/all?version_prefix=15.0', admin, admin_mode: true)
+
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner with machine', 'active' => true, 'paused' => false)
+ ]
+ end
+
+ it 'filters runners by version_prefix when prefix is "16"' do
+ get api('/runners/all?version_prefix=16', admin, admin_mode: true)
+ expect(json_response).to match_array [
+ a_hash_including('description' => 'Runner with machine version 16', 'active' => true, 'paused' => false)
+ ]
+ end
+
+ it 'filters runners by version_prefix when prefix is "25"' do
+ get api('/runners/all?version_prefix=25', admin, admin_mode: true)
+ expect(json_response).to match_array []
+ end
+
+ it 'does not filter runners by version_prefix when prefix is invalid ("V15")' do
+ get api('/runners/all?version_prefix=v15', admin, admin_mode: true)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
end
context 'without admin privileges' do
@@ -467,13 +500,17 @@ RSpec.describe API::Ci::Runners, :aggregate_failures, feature_category: :runner_
active = shared_runner.active
runner_queue_value = shared_runner.ensure_runner_queue_value
- update_runner(shared_runner.id, admin, description: "#{description}_updated",
- active: !active,
- tag_list: ['ruby2.1', 'pgsql', 'mysql'],
- run_untagged: 'false',
- locked: 'true',
- access_level: 'ref_protected',
- maximum_timeout: 1234)
+ update_runner(
+ shared_runner.id,
+ admin,
+ description: "#{description}_updated",
+ active: !active,
+ tag_list: ['ruby2.1', 'pgsql', 'mysql'],
+ run_untagged: 'false',
+ locked: 'true',
+ access_level: 'ref_protected',
+ maximum_timeout: 1234
+ )
shared_runner.reload
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/requests/api/ci/triggers_spec.rb b/spec/requests/api/ci/triggers_spec.rb
index ff54ba61309..a6e50479963 100644
--- a/spec/requests/api/ci/triggers_spec.rb
+++ b/spec/requests/api/ci/triggers_spec.rb
@@ -81,7 +81,7 @@ RSpec.describe API::Ci::Triggers, feature_category: :continuous_integration do
end
it 'validates variables needs to be a map of key-valued strings' do
- post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(variables: { 'TRIGGER_KEY' => %w(1 2) }, ref: 'master')
+ post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(variables: { 'TRIGGER_KEY' => %w[1 2] }, ref: 'master')
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index c3a7dbdcdbb..6a112918288 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -2426,16 +2426,6 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
expect(json_response['x509_certificate']['x509_issuer']['crl_url']).to eq(commit.signature.x509_certificate.x509_issuer.crl_url)
expect(json_response['commit_source']).to eq('gitaly')
end
-
- context 'with Rugged enabled', :enable_rugged do
- it 'returns correct JSON' do
- get api(route, current_user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['signature_type']).to eq('X509')
- expect(json_response['commit_source']).to eq('gitaly')
- end
- end
end
context 'with ssh signed commit' do
diff --git a/spec/requests/api/container_repositories_spec.rb b/spec/requests/api/container_repositories_spec.rb
index 605fa0d92f6..82c63362166 100644
--- a/spec/requests/api/container_repositories_spec.rb
+++ b/spec/requests/api/container_repositories_spec.rb
@@ -67,7 +67,7 @@ RSpec.describe API::ContainerRepositories, feature_category: :container_registry
let(:url) { "/registry/repositories/#{repository.id}?tags=true" }
before do
- stub_container_registry_tags(repository: repository.path, tags: %w(rootA latest), with_manifest: true)
+ stub_container_registry_tags(repository: repository.path, tags: %w[rootA latest], with_manifest: true)
end
it 'returns a repository and its tags' do
@@ -102,7 +102,7 @@ RSpec.describe API::ContainerRepositories, feature_category: :container_registry
let(:url) { "/registry/repositories/#{repository.id}?tags_count=true" }
before do
- stub_container_registry_tags(repository: repository.path, tags: %w(rootA latest), with_manifest: true)
+ stub_container_registry_tags(repository: repository.path, tags: %w[rootA latest], with_manifest: true)
end
it 'returns a repository and its tags_count' do
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index 82ac2eed83d..41c5847e940 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -424,7 +424,7 @@ RSpec.describe API::Deployments, feature_category: :continuous_delivery do
)
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['status']).to include(%{cannot transition via \"run\"})
+ expect(json_response['message']['status']).to include(%(cannot transition via \"run\"))
end
it 'links merge requests when the deployment status changes to success', :sidekiq_inline do
diff --git a/spec/requests/api/geo_spec.rb b/spec/requests/api/geo_spec.rb
index 3dec91fd2fa..c394553d14e 100644
--- a/spec/requests/api/geo_spec.rb
+++ b/spec/requests/api/geo_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe API::Geo, feature_category: :geo_replication do
{
'type' => 'object',
'additionalProperties' => false,
- 'required' => %w(geo_enabled),
+ 'required' => %w[geo_enabled],
'properties' => {
'geo_enabled' => { 'type' => 'boolean' }
}
diff --git a/spec/requests/api/graphql/abuse_report_spec.rb b/spec/requests/api/graphql/abuse_report_spec.rb
index 7d0b8b35763..f74b1fb4061 100644
--- a/spec/requests/api/graphql/abuse_report_spec.rb
+++ b/spec/requests/api/graphql/abuse_report_spec.rb
@@ -2,49 +2,122 @@
require 'spec_helper'
-RSpec.describe 'abuse_report', feature_category: :insider_threat do
+RSpec.describe 'Querying an Abuse Report', feature_category: :insider_threat do
include GraphqlHelpers
let_it_be(:current_user) { create(:admin) }
- let_it_be(:label) { create(:abuse_report_label, title: 'Uno') }
- let_it_be(:report) { create(:abuse_report, labels: [label]) }
-
- let(:report_gid) { Gitlab::GlobalId.build(report, id: report.id).to_s }
-
- let(:fields) do
- <<~GRAPHQL
- labels {
- nodes {
- id
- title
- description
- color
- textColor
- }
- }
- GRAPHQL
- end
+ let_it_be(:abuse_report) { create(:abuse_report) }
- let(:arguments) { { id: report_gid } }
- let(:query) { graphql_query_for('abuseReport', arguments, fields) }
+ let(:global_id) { abuse_report.to_gid.to_s }
+ let(:abuse_report_fields) { all_graphql_fields_for('AbuseReport', max_depth: 2) }
+ let(:abuse_report_data) { graphql_data['abuseReport'] }
+
+ let(:query) do
+ graphql_query_for('abuseReport', { 'id' => global_id }, abuse_report_fields)
+ end
before do
post_graphql(query, current_user: current_user)
end
- it_behaves_like 'a working graphql query that returns data'
+ context 'when the user is an admin' do
+ it_behaves_like 'a working graphql query that returns data'
- it 'returns abuse report with labels' do
- expect(graphql_data_at('abuseReport', 'labels', 'nodes', 0)).to match(a_graphql_entity_for(label))
+ it 'returns all fields' do
+ expect(abuse_report_data).to include(
+ 'id' => global_id,
+ 'userPermissions' => {
+ 'readAbuseReport' => true,
+ 'createNote' => true
+ }
+ )
+ end
end
- context 'when current user is not an admin' do
- let_it_be(:current_user) { create(:user) }
+ context 'when the user is not an admin' do
+ let(:current_user) { create(:user) }
+
+ it 'returns nil' do
+ expect(abuse_report_data).to be_nil
+ end
+ end
- it_behaves_like 'a working graphql query'
+ describe 'labels' do
+ let_it_be(:abuse_report_label) { create(:abuse_report_label, title: 'Label') }
+ let_it_be(:abuse_report) { create(:abuse_report, labels: [abuse_report_label]) }
+
+ let(:labels_response) do
+ graphql_data_at(:abuse_report, :labels, :nodes)
+ end
+
+ let(:abuse_report_fields) do
+ <<~GRAPHQL
+ labels {
+ nodes {
+ id
+ title
+ description
+ color
+ textColor
+ }
+ }
+ GRAPHQL
+ end
+
+ it 'returns labels' do
+ expect(labels_response).to contain_exactly(
+ a_graphql_entity_for(abuse_report_label)
+ )
+ end
+ end
+
+ describe 'notes' do
+ let_it_be(:note) { create(:note, noteable: abuse_report, author: current_user) }
+
+ let(:notes_response) do
+ graphql_data_at(:abuse_report, :notes, :nodes)
+ end
+
+ let(:abuse_report_fields) do
+ <<~GRAPHQL
+ notes {
+ nodes {
+ #{all_graphql_fields_for('Note', max_depth: 2)}
+ }
+ }
+ GRAPHQL
+ end
+
+ it 'returns notes' do
+ expect(notes_response).to contain_exactly(
+ a_graphql_entity_for(note)
+ )
+ end
+ end
+
+ describe 'discussions' do
+ let_it_be(:discussion) do
+ create(:discussion_note_on_abuse_report, noteable: abuse_report, author: current_user).to_discussion
+ end
+
+ let(:discussions_response) do
+ graphql_data_at(:abuse_report, :discussions, :nodes)
+ end
+
+ let(:abuse_report_fields) do
+ <<~GRAPHQL
+ discussions {
+ nodes {
+ #{all_graphql_fields_for('Discussion', max_depth: 2)}
+ }
+ }
+ GRAPHQL
+ end
- it 'does not contain any data' do
- expect(graphql_data_at('abuseReportLabel')).to be_nil
+ it 'returns discussions' do
+ expect(discussions_response).to contain_exactly(
+ a_graphql_entity_for(discussion)
+ )
end
end
end
diff --git a/spec/requests/api/graphql/ci/catalog/resource_spec.rb b/spec/requests/api/graphql/ci/catalog/resource_spec.rb
new file mode 100644
index 00000000000..fce773f320b
--- /dev/null
+++ b/spec/requests/api/graphql/ci/catalog/resource_spec.rb
@@ -0,0 +1,341 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.ciCatalogResource', feature_category: :pipeline_composition do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:namespace) { create(:group) }
+
+ let_it_be(:project) do
+ create(
+ :project, :with_avatar, :custom_repo,
+ name: 'Component Repository',
+ description: 'A simple component',
+ namespace: namespace,
+ star_count: 1,
+ files: { 'README.md' => '[link](README.md)' }
+ )
+ end
+
+ let_it_be(:resource) { create(:ci_catalog_resource, project: project) }
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(id: "#{resource.to_global_id}") {
+ #{all_graphql_fields_for('CiCatalogResource', max_depth: 1)}
+ }
+ }
+ GQL
+ end
+
+ subject(:post_query) { post_graphql(query, current_user: user) }
+
+ context 'when the current user has permission to read the namespace catalog' do
+ it 'returns the resource with the expected data' do
+ namespace.add_developer(user)
+
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(
+ resource, :name, :description,
+ icon: project.avatar_path,
+ webPath: "/#{project.full_path}",
+ starCount: project.star_count,
+ forksCount: project.forks_count,
+ readmeHtml: a_string_including(
+ "#{project.full_path}/-/blob/#{project.default_branch}/README.md"
+ )
+ )
+ )
+ end
+ end
+
+ context 'when the current user does not have permission to read the namespace catalog' do
+ it 'returns nil' do
+ namespace.add_guest(user)
+
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to be_nil
+ end
+ end
+
+ describe 'versions' do
+ before_all do
+ namespace.add_developer(user)
+ end
+
+ before do
+ stub_licensed_features(ci_namespace_catalog: true)
+ end
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(id: "#{resource.to_global_id}") {
+ id
+ versions {
+ nodes {
+ id
+ tagName
+ releasedAt
+ author {
+ id
+ name
+ webUrl
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ context 'when the resource has versions' do
+ let_it_be(:author) { create(:user, name: 'author') }
+
+ let_it_be(:version1) do
+ create(:release, project: project, released_at: '2023-01-01T00:00:00Z', author: author)
+ end
+
+ let_it_be(:version2) do
+ create(:release, project: project, released_at: '2023-02-01T00:00:00Z', author: author)
+ end
+
+ it 'returns the resource with the versions data' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(resource)
+ )
+
+ expect(graphql_data_at(:ciCatalogResource, :versions, :nodes)).to contain_exactly(
+ a_graphql_entity_for(
+ version1,
+ tagName: version1.tag,
+ releasedAt: version1.released_at,
+ author: a_graphql_entity_for(author, :name)
+ ),
+ a_graphql_entity_for(
+ version2,
+ tagName: version2.tag,
+ releasedAt: version2.released_at,
+ author: a_graphql_entity_for(author, :name)
+ )
+ )
+ end
+ end
+
+ context 'when the resource does not have a version' do
+ it 'returns versions as an empty array' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(resource, versions: { 'nodes' => [] })
+ )
+ end
+ end
+ end
+
+ describe 'latestVersion' do
+ before_all do
+ namespace.add_developer(user)
+ end
+
+ before do
+ stub_licensed_features(ci_namespace_catalog: true)
+ end
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(id: "#{resource.to_global_id}") {
+ id
+ latestVersion {
+ id
+ tagName
+ releasedAt
+ author {
+ id
+ name
+ webUrl
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ context 'when the resource has versions' do
+ let_it_be(:author) { create(:user, name: 'author') }
+
+ let_it_be(:latest_version) do
+ create(:release, project: project, released_at: '2023-02-01T00:00:00Z', author: author)
+ end
+
+ before_all do
+ # Previous version of the project
+ create(:release, project: project, released_at: '2023-01-01T00:00:00Z', author: author)
+ end
+
+ it 'returns the resource with the latest version data' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(
+ resource,
+ latestVersion: a_graphql_entity_for(
+ latest_version,
+ tagName: latest_version.tag,
+ releasedAt: latest_version.released_at,
+ author: a_graphql_entity_for(author, :name)
+ )
+ )
+ )
+ end
+ end
+
+ context 'when the resource does not have a version' do
+ it 'returns nil' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(resource, latestVersion: nil)
+ )
+ end
+ end
+ end
+
+ describe 'rootNamespace' do
+ before_all do
+ namespace.add_developer(user)
+ end
+
+ before do
+ stub_licensed_features(ci_namespace_catalog: true)
+ end
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(id: "#{resource.to_global_id}") {
+ id
+ rootNamespace {
+ id
+ name
+ path
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'returns the correct root namespace data' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(
+ resource,
+ rootNamespace: a_graphql_entity_for(namespace, :name, :path)
+ )
+ )
+ end
+ end
+
+ describe 'openIssuesCount' do
+ before do
+ stub_licensed_features(ci_namespace_catalog: true)
+ end
+
+ context 'when open_issue_count is requested' do
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(id: "#{resource.to_global_id}") {
+ openIssuesCount
+ }
+ }
+ GQL
+ end
+
+ it 'returns the correct count' do
+ create(:issue, :opened, project: project)
+ create(:issue, :opened, project: project)
+
+ namespace.add_developer(user)
+
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(
+ open_issues_count: 2
+ )
+ )
+ end
+
+ context 'when open_issue_count is zero' do
+ it 'returns zero' do
+ namespace.add_developer(user)
+
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(
+ open_issues_count: 0
+ )
+ )
+ end
+ end
+ end
+ end
+
+ describe 'openMergeRequestsCount' do
+ before do
+ stub_licensed_features(ci_namespace_catalog: true)
+ end
+
+ context 'when merge_requests_count is requested' do
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResource(id: "#{resource.to_global_id}") {
+ openMergeRequestsCount
+ }
+ }
+ GQL
+ end
+
+ it 'returns the correct count' do
+ create(:merge_request, :opened, source_project: project)
+
+ namespace.add_developer(user)
+
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(
+ open_merge_requests_count: 1
+ )
+ )
+ end
+
+ context 'when open merge_requests_count is zero' do
+ it 'returns zero' do
+ namespace.add_developer(user)
+
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResource)).to match(
+ a_graphql_entity_for(
+ open_merge_requests_count: 0
+ )
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/ci/catalog/resources_spec.rb b/spec/requests/api/graphql/ci/catalog/resources_spec.rb
new file mode 100644
index 00000000000..7c955a1202c
--- /dev/null
+++ b/spec/requests/api/graphql/ci/catalog/resources_spec.rb
@@ -0,0 +1,359 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.ciCatalogResources', feature_category: :pipeline_composition do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:namespace) { create(:group) }
+ let_it_be(:project2) { create(:project, namespace: namespace) }
+
+ let_it_be(:project1) do
+ create(
+ :project, :with_avatar, :custom_repo,
+ name: 'Component Repository',
+ description: 'A simple component',
+ namespace: namespace,
+ star_count: 1,
+ files: { 'README.md' => '**Test**' }
+ )
+ end
+
+ let_it_be(:public_project) do
+ create(
+ :project, :with_avatar, :custom_repo, :public,
+ name: 'Public Component',
+ description: 'A public component',
+ files: { 'README.md' => '**Test**' }
+ )
+ end
+
+ let_it_be(:resource1) { create(:ci_catalog_resource, project: project1, latest_released_at: '2023-01-01T00:00:00Z') }
+ let_it_be(:public_resource) { create(:ci_catalog_resource, project: public_project) }
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResources {
+ nodes {
+ id
+ name
+ description
+ icon
+ webPath
+ latestReleasedAt
+ starCount
+ forksCount
+ readmeHtml
+ }
+ }
+ }
+ GQL
+ end
+
+ subject(:post_query) { post_graphql(query, current_user: user) }
+
+ shared_examples 'avoids N+1 queries' do
+ it do
+ ctx = { current_user: user }
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ run_with_clean_state(query, context: ctx)
+ end
+
+ create(:ci_catalog_resource, project: project2)
+
+ expect do
+ run_with_clean_state(query, context: ctx)
+ end.not_to exceed_query_limit(control_count)
+ end
+ end
+
+ it_behaves_like 'avoids N+1 queries'
+
+ it 'returns the resources with the expected data' do
+ namespace.add_developer(user)
+
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly(
+ a_graphql_entity_for(
+ resource1, :name, :description,
+ icon: project1.avatar_path,
+ webPath: "/#{project1.full_path}",
+ starCount: project1.star_count,
+ forksCount: project1.forks_count,
+ readmeHtml: a_string_including('Test</strong>'),
+ latestReleasedAt: resource1.latest_released_at
+ ),
+ a_graphql_entity_for(public_resource)
+ )
+ end
+
+ describe 'versions' do
+ before_all do
+ namespace.add_developer(user)
+ end
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResources {
+ nodes {
+ id
+ versions {
+ nodes {
+ id
+ tagName
+ releasedAt
+ author {
+ id
+ name
+ webUrl
+ }
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'limits the request to 1 resource at a time' do
+ create(:ci_catalog_resource, project: project2)
+
+ post_query
+
+ expect_graphql_errors_to_include \
+ [/"versions" field can be requested only for 1 CiCatalogResource\(s\) at a time./]
+ end
+ end
+
+ describe 'latestVersion' do
+ let_it_be(:author1) { create(:user, name: 'author1') }
+ let_it_be(:author2) { create(:user, name: 'author2') }
+
+ let_it_be(:latest_version1) do
+ create(:release, project: project1, released_at: '2023-02-01T00:00:00Z', author: author1)
+ end
+
+ let_it_be(:latest_version2) do
+ create(:release, project: public_project, released_at: '2023-02-01T00:00:00Z', author: author2)
+ end
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResources {
+ nodes {
+ id
+ latestVersion {
+ id
+ tagName
+ releasedAt
+ author {
+ id
+ name
+ webUrl
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ before_all do
+ namespace.add_developer(user)
+
+ # Previous versions of the projects
+ create(:release, project: project1, released_at: '2023-01-01T00:00:00Z', author: author1)
+ create(:release, project: public_project, released_at: '2023-01-01T00:00:00Z', author: author2)
+ end
+
+ it 'returns all resources with the latest version data' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly(
+ a_graphql_entity_for(
+ resource1,
+ latestVersion: a_graphql_entity_for(
+ latest_version1,
+ tagName: latest_version1.tag,
+ releasedAt: latest_version1.released_at,
+ author: a_graphql_entity_for(author1, :name)
+ )
+ ),
+ a_graphql_entity_for(
+ public_resource,
+ latestVersion: a_graphql_entity_for(
+ latest_version2,
+ tagName: latest_version2.tag,
+ releasedAt: latest_version2.released_at,
+ author: a_graphql_entity_for(author2, :name)
+ )
+ )
+ )
+ end
+
+ # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/430350
+ # it_behaves_like 'avoids N+1 queries'
+ end
+
+ describe 'rootNamespace' do
+ before_all do
+ namespace.add_developer(user)
+ end
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResources {
+ nodes {
+ id
+ rootNamespace {
+ id
+ name
+ path
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'returns the correct root namespace data' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly(
+ a_graphql_entity_for(
+ resource1,
+ rootNamespace: a_graphql_entity_for(namespace, :name, :path)
+ ),
+ a_graphql_entity_for(public_resource, rootNamespace: nil)
+ )
+ end
+ end
+
+ describe 'openIssuesCount' do
+ before_all do
+ namespace.add_developer(user)
+ end
+
+ before_all do
+ create(:issue, :opened, project: project1)
+ create(:issue, :opened, project: project1)
+
+ create(:issue, :opened, project: public_project)
+ end
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResources {
+ nodes {
+ openIssuesCount
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'returns the correct count' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly(
+ a_graphql_entity_for(openIssuesCount: 2),
+ a_graphql_entity_for(openIssuesCount: 1)
+ )
+ end
+
+ it_behaves_like 'avoids N+1 queries'
+ end
+
+ describe 'openMergeRequestsCount' do
+ before_all do
+ namespace.add_developer(user)
+ end
+
+ before_all do
+ create(:merge_request, :opened, source_project: project1)
+ create(:merge_request, :opened, source_project: public_project)
+ end
+
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResources {
+ nodes {
+ openMergeRequestsCount
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'returns the correct count' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly(
+ a_graphql_entity_for(openMergeRequestsCount: 1),
+ a_graphql_entity_for(openMergeRequestsCount: 1)
+ )
+ end
+
+ it_behaves_like 'avoids N+1 queries'
+ end
+
+ # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/429636
+ context 'when using `projectPath` (legacy) to fetch resources' do
+ let(:query) do
+ <<~GQL
+ query {
+ ciCatalogResources(projectPath: "#{project1.full_path}") {
+ nodes {
+ #{all_graphql_fields_for('CiCatalogResource', max_depth: 1)}
+ }
+ }
+ }
+ GQL
+ end
+
+ context 'when the current user has permission to read the namespace catalog' do
+ before_all do
+ namespace.add_developer(user)
+ end
+
+ it 'returns catalog resources with the expected data' do
+ resource2 = create(:ci_catalog_resource, project: project2)
+ _resource_in_another_namespace = create(:ci_catalog_resource)
+
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResources, :nodes)).to contain_exactly(
+ a_graphql_entity_for(resource1),
+ a_graphql_entity_for(
+ resource2, :name, :description,
+ icon: project2.avatar_path,
+ webPath: "/#{project2.full_path}",
+ starCount: project2.star_count,
+ forksCount: project2.forks_count,
+ readmeHtml: '',
+ latestReleasedAt: resource2.latest_released_at
+ )
+ )
+ end
+
+ it_behaves_like 'avoids N+1 queries'
+ end
+
+ context 'when the current user does not have permission to read the namespace catalog' do
+ it 'returns no resources' do
+ post_query
+
+ expect(graphql_data_at(:ciCatalogResources, :nodes)).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/ci/manual_variables_spec.rb b/spec/requests/api/graphql/ci/manual_variables_spec.rb
index 47dccc0deb6..41788881e62 100644
--- a/spec/requests/api/graphql/ci/manual_variables_spec.rb
+++ b/spec/requests/api/graphql/ci/manual_variables_spec.rb
@@ -90,6 +90,6 @@ RSpec.describe 'Query.project(fullPath).pipelines.jobs.manualVariables', feature
variables_data = graphql_data.dig('project', 'pipelines', 'nodes').first
.dig('jobs', 'nodes').flat_map { |job| job.dig('manualVariables', 'nodes') }
- expect(variables_data.map { |var| var['key'] }).to match_array(%w(MANUAL_TEST_VAR_1 MANUAL_TEST_VAR_2))
+ expect(variables_data.map { |var| var['key'] }).to match_array(%w[MANUAL_TEST_VAR_1 MANUAL_TEST_VAR_2])
end
end
diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb
index c5571086700..0e2712d742d 100644
--- a/spec/requests/api/graphql/ci/runners_spec.rb
+++ b/spec/requests/api/graphql/ci/runners_spec.rb
@@ -35,28 +35,16 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do
end
context 'with filters' do
- let(:query) do
- %(
- query {
- runners(type: #{runner_type}, status: #{status}) {
- #{fields}
- }
- }
- )
- end
-
- before do
- allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance|
- allow(instance).to receive(:check_runner_upgrade_suggestion)
- end
-
- post_graphql(query, current_user: current_user)
- end
-
shared_examples 'a working graphql query returning expected runner' do
- it_behaves_like 'a working graphql query'
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
it 'returns expected runner' do
+ post_graphql(query, current_user: current_user)
+
expect(runners_graphql_data['nodes']).to contain_exactly(a_graphql_entity_for(expected_runner))
end
@@ -86,22 +74,63 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do
end
end
- context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do
- let(:runner_type) { 'INSTANCE_TYPE' }
- let(:status) { 'ACTIVE' }
+ context 'when filtered on type and status' do
+ let(:query) do
+ %(
+ query {
+ runners(type: #{runner_type}, status: #{status}) {
+ #{fields}
+ }
+ }
+ )
+ end
- let!(:expected_runner) { instance_runner }
+ before do
+ allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance|
+ allow(instance).to receive(:check_runner_upgrade_suggestion)
+ end
+ end
- it_behaves_like 'a working graphql query returning expected runner'
+ context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do
+ let(:runner_type) { 'INSTANCE_TYPE' }
+ let(:status) { 'ACTIVE' }
+
+ let!(:expected_runner) { instance_runner }
+
+ 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
- context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do
- let(:runner_type) { 'PROJECT_TYPE' }
- let(:status) { 'NEVER_CONTACTED' }
+ context 'when filtered on version prefix' do
+ let_it_be(:version_runner) { create(:ci_runner, :project, active: false, description: 'Runner with machine') }
+ let_it_be(:version_runner_machine) { create(:ci_runner_machine, runner: version_runner, version: '15.11.0') }
+
+ let(:query) do
+ %(
+ query {
+ runners(versionPrefix: "#{version_prefix}") {
+ #{fields}
+ }
+ }
+ )
+ end
+
+ context 'version_prefix is "15."' do
+ let(:version_prefix) { '15.' }
- let!(:expected_runner) { project_runner }
+ let!(:expected_runner) { version_runner }
- it_behaves_like 'a working graphql query returning expected runner'
+ it_behaves_like 'a working graphql query returning expected runner'
+ end
end
end
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 118a11851dd..20277c7e27b 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
@@ -313,4 +313,124 @@ RSpec.describe 'container repository details', feature_category: :container_regi
end
it_behaves_like 'handling graphql network errors with the container registry'
+
+ context 'when list tags API is enabled', :saas do
+ before do
+ stub_container_registry_config(enabled: true)
+ allow(ContainerRegistry::GitlabApiClient).to receive(:supports_gitlab_api?).and_return(true)
+
+ allow_next_instances_of(ContainerRegistry::GitlabApiClient, nil) do |client|
+ allow(client).to receive(:tags).and_return(response_body)
+ end
+ end
+
+ let_it_be(:raw_tags_response) do
+ [
+ {
+ name: 'latest',
+ digest: 'sha256:1234567892',
+ config_digest: 'sha256:3332132331',
+ media_type: 'application/vnd.oci.image.manifest.v1+json',
+ size_bytes: 1234567892,
+ created_at: 10.minutes.ago,
+ updated_at: 10.minutes.ago
+ }
+ ]
+ end
+
+ let_it_be(:url) { URI('/gitlab/v1/repositories/group1/proj1/tags/list/?before=tag1') }
+
+ let_it_be(:response_body) do
+ {
+ pagination: { previous: { uri: url }, next: { uri: url } },
+ response_body: ::Gitlab::Json.parse(raw_tags_response.to_json)
+ }
+ end
+
+ it_behaves_like 'a working graphql query' do # OK
+ before do
+ subject
+ end
+
+ it 'matches the JSON schema' do
+ expect(container_repository_details_response).to match_schema('graphql/container_repository_details')
+ end
+ end
+
+ context 'with different permissions' do # OK
+ let_it_be(:user) { create(:user) }
+
+ let(:tags_response) { container_repository_details_response.dig('tags', 'nodes') }
+
+ where(:project_visibility, :role, :access_granted, :can_delete) do
+ :private | :maintainer | true | true
+ :private | :developer | true | true
+ :private | :reporter | true | false
+ :private | :guest | false | false
+ :private | :anonymous | false | false
+ :public | :maintainer | true | true
+ :public | :developer | true | true
+ :public | :reporter | true | false
+ :public | :guest | true | false
+ :public | :anonymous | true | false
+ end
+
+ with_them do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility.to_s.upcase, false))
+ project.add_member(user, role) unless role == :anonymous
+ end
+
+ it 'return the proper response' do
+ subject
+
+ if access_granted
+ expect(tags_response.size).to eq(raw_tags_response.size)
+ expect(container_repository_details_response.dig('canDelete')).to eq(can_delete)
+ else
+ expect(container_repository_details_response).to eq(nil)
+ end
+ end
+ end
+ end
+
+ context 'querying' 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: ContainerRepositoryID!, $n: String) {
+ containerRepository(id: $id) {
+ tags(name: $n) {
+ edges {
+ node {
+ #{all_graphql_fields_for('ContainerRepositoryTag')}
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'returns the tag response', :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
+
+ it_behaves_like 'handling graphql network errors with the container registry'
+ end
end
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index d55a70f503c..060a1b42cb6 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -113,7 +113,7 @@ RSpec.describe 'GitlabSchema configurations', feature_category: :integrations do
context 'regular queries' do
subject do
- query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id name description))
+ query = graphql_query_for('project', { 'fullPath' => project.full_path }, %w[id name description])
post_graphql(query)
end
@@ -125,7 +125,7 @@ RSpec.describe 'GitlabSchema configurations', feature_category: :integrations do
subject do
queries = [
- { query: graphql_query_for('project', { 'fullPath' => '$fullPath' }, %w(id name description)) }, # Complexity 4
+ { query: graphql_query_for('project', { 'fullPath' => '$fullPath' }, %w[id name description]) }, # Complexity 4
{ query: graphql_query_for('echo', { 'text' => "$test" }, []), variables: { "test" => "Hello world" } }, # Complexity 1
{ query: graphql_query_for('project', { 'fullPath' => project.full_path }, "userPermissions { createIssue }") } # Complexity 3
]
@@ -215,7 +215,7 @@ RSpec.describe 'GitlabSchema configurations', feature_category: :integrations do
context "global id's" do
it 'uses GlobalID to expose ids' do
- post_graphql(graphql_query_for('project', { 'fullPath' => project.full_path }, %w(id)),
+ post_graphql(graphql_query_for('project', { 'fullPath' => project.full_path }, %w[id]),
current_user: project.first_owner)
parsed_id = GlobalID.parse(graphql_data['project']['id'])
diff --git a/spec/requests/api/graphql/group/container_repositories_spec.rb b/spec/requests/api/graphql/group/container_repositories_spec.rb
index 51d12261247..9206ead1534 100644
--- a/spec/requests/api/graphql/group/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/group/container_repositories_spec.rb
@@ -49,7 +49,7 @@ RSpec.describe 'getting container repositories in a group', feature_category: :s
group.add_owner(owner)
stub_container_registry_config(enabled: true)
container_repositories.each do |repository|
- stub_container_registry_tags(repository: repository.path, tags: %w(tag1 tag2 tag3), with_manifest: false)
+ stub_container_registry_tags(repository: repository.path, tags: %w[tag1 tag2 tag3], with_manifest: false)
end
end
@@ -142,7 +142,7 @@ RSpec.describe 'getting container repositories in a group', feature_category: :s
end
before do
- stub_container_registry_tags(repository: container_repository.path, tags: %w(tag4 tag5 tag6), with_manifest: false)
+ stub_container_registry_tags(repository: container_repository.path, tags: %w[tag4 tag5 tag6], with_manifest: false)
end
it 'returns the searched container repository' do
diff --git a/spec/requests/api/graphql/group/data_transfer_spec.rb b/spec/requests/api/graphql/group/data_transfer_spec.rb
index b7c038afa54..e17074a0247 100644
--- a/spec/requests/api/graphql/group/data_transfer_spec.rb
+++ b/spec/requests/api/graphql/group/data_transfer_spec.rb
@@ -71,45 +71,21 @@ RSpec.describe 'group data transfers', feature_category: :source_code_management
context 'when user has enough permissions' do
before do
group.add_owner(current_user)
+ subject
end
- context 'when data_transfer_monitoring_mock_data is NOT enabled' do
- before do
- stub_feature_flags(data_transfer_monitoring_mock_data: false)
- subject
- end
-
- it 'returns real results' do
- expect(response).to have_gitlab_http_status(:ok)
+ it 'returns real results' do
+ expect(response).to have_gitlab_http_status(:ok)
- expect(egress_data.count).to eq(2)
+ expect(egress_data.count).to eq(2)
- expect(egress_data.first.keys).to match_array(
- %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress]
- )
+ expect(egress_data.first.keys).to match_array(
+ %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress]
+ )
- expect(egress_data.pluck('repositoryEgress')).to match_array(%w[1 6])
- end
-
- it_behaves_like 'a working graphql query'
+ expect(egress_data.pluck('repositoryEgress')).to match_array(%w[1 6])
end
- context 'when data_transfer_monitoring_mock_data is enabled' do
- before do
- stub_feature_flags(data_transfer_monitoring_mock_data: true)
- subject
- end
-
- it 'returns mock results' do
- expect(response).to have_gitlab_http_status(:ok)
-
- expect(egress_data.count).to eq(12)
- expect(egress_data.first.keys).to match_array(
- %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress]
- )
- end
-
- it_behaves_like 'a working graphql query'
- end
+ it_behaves_like 'a working graphql query'
end
end
diff --git a/spec/requests/api/graphql/group/milestones_spec.rb b/spec/requests/api/graphql/group/milestones_spec.rb
index 209588835f2..6063e6d5293 100644
--- a/spec/requests/api/graphql/group/milestones_spec.rb
+++ b/spec/requests/api/graphql/group/milestones_spec.rb
@@ -136,7 +136,7 @@ RSpec.describe 'Milestones through GroupQuery', feature_category: :team_planning
let_it_be(:closed_issue) { create(:issue, :closed, project: project, milestone: milestone) }
let(:milestone_query) do
- %{
+ %(
id
title
description
@@ -149,7 +149,7 @@ RSpec.describe 'Milestones through GroupQuery', feature_category: :team_planning
projectMilestone
groupMilestone
subgroupMilestone
- }
+ )
end
def post_query
@@ -180,12 +180,12 @@ RSpec.describe 'Milestones through GroupQuery', feature_category: :team_planning
context 'milestone statistics' do
let(:milestone_query) do
- %{
+ %(
stats {
totalIssuesCount
closedIssuesCount
}
- }
+ )
end
it 'returns the correct milestone statistics' do
diff --git a/spec/requests/api/graphql/merge_requests/codequality_reports_comparer_spec.rb b/spec/requests/api/graphql/merge_requests/codequality_reports_comparer_spec.rb
index 2939e9307e9..09a229c2098 100644
--- a/spec/requests/api/graphql/merge_requests/codequality_reports_comparer_spec.rb
+++ b/spec/requests/api/graphql/merge_requests/codequality_reports_comparer_spec.rb
@@ -54,6 +54,7 @@ RSpec.describe 'Query.project.mergeRequest.codequalityReportsComparer', feature_
let(:codequality_reports_comparer_fields) do
<<~QUERY
codequalityReportsComparer {
+ status
report {
status
newErrors {
@@ -138,6 +139,7 @@ RSpec.describe 'Query.project.mergeRequest.codequalityReportsComparer', feature_
expect(result).to match(
a_hash_including(
{
+ status: 'PARSED',
report: {
status: 'FAILED',
newErrors: [
diff --git a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
index e3a7442ffe6..316b0f3755d 100644
--- a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
+++ b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate
let(:current_user) { create(:user) }
it_behaves_like 'a mutation that returns top-level errors',
- errors: ['You must be an admin to use this mutation']
+ errors: ['You must be an admin to use this mutation']
end
context 'when the user is an admin' do
@@ -43,7 +43,7 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate
raise 'Not enqueued!' if Sidekiq::Queue.new(queue).size.zero?
end
- it 'returns info about the deleted jobs', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/425824' do
+ it 'returns info about the deleted jobs' do
add_job(admin, [1])
add_job(admin, [2])
add_job(create(:user), [3])
@@ -51,9 +51,7 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate
post_graphql_mutation(mutation, current_user: admin)
expect(mutation_response['errors']).to be_empty
- expect(mutation_response['result']).to eq('completed' => true,
- 'deletedJobs' => 2,
- 'queueSize' => 1)
+ expect(mutation_response['result']).to eq('completed' => true, 'deletedJobs' => 2, 'queueSize' => 1)
end
end
@@ -61,14 +59,14 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate
let(:variables) { { queue_name: queue } }
it_behaves_like 'a mutation that returns errors in the response',
- errors: ['No metadata provided']
+ errors: ['No metadata provided']
end
context 'when the queue does not exist' do
let(:variables) { { user: admin.username, queue_name: 'authorized_projects_2' } }
it_behaves_like 'a mutation that returns top-level errors',
- errors: ['Queue authorized_projects_2 not found']
+ errors: ['Queue authorized_projects_2 not found']
end
end
end
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb
index fbe6d95dfff..f2b516783e5 100644
--- a/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb
@@ -14,21 +14,23 @@ RSpec.describe 'Create an alert issue from an alert', feature_category: :inciden
project_path: project.full_path,
iid: alert.iid.to_s
}
- graphql_mutation(:create_alert_issue, variables,
- <<~QL
- clientMutationId
- errors
- alert {
- iid
- issue {
- iid
- }
- }
- issue {
- iid
- title
- }
- QL
+ graphql_mutation(
+ :create_alert_issue,
+ variables,
+ <<~QL
+ clientMutationId
+ errors
+ alert {
+ iid
+ issue {
+ iid
+ }
+ }
+ issue {
+ iid
+ title
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
index 65a5fb87f9a..e7e23304d81 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -6,9 +6,11 @@ RSpec.describe 'Toggling an AwardEmoji', feature_category: :shared do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
- let_it_be(:project, reload: true) { create(:project) }
- let_it_be(:awardable) { create(:note, project: project) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project, reload: true) { create(:project, group: group) }
+ let_it_be(:issue_note) { create(:note, project: project) }
+ let(:awardable) { issue_note }
let(:emoji_name) { 'thumbsup' }
let(:mutation) do
variables = {
@@ -36,8 +38,8 @@ RSpec.describe 'Toggling an AwardEmoji', feature_category: :shared do
end
context 'when the user has permission' do
- before do
- project.add_developer(current_user)
+ before_all do
+ group.add_developer(current_user)
end
context 'when the given awardable is not an Awardable' do
@@ -60,6 +62,33 @@ RSpec.describe 'Toggling an AwardEmoji', feature_category: :shared do
end
context 'when the given awardable is an Awardable' do
+ context 'when the awardable is a work item' do
+ context 'when the work item is associated directly with a group' do
+ let_it_be(:group_work_item) { create(:work_item, :group_level, namespace: group) }
+ let(:awardable) { group_work_item }
+
+ context 'when no emoji has been awarded by the current_user yet' do
+ it 'creates an emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(1)
+ end
+ end
+
+ context 'when an emoji has been awarded by the current_user' do
+ before do
+ create_award_emoji(current_user)
+ end
+
+ it 'removes the emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(-1)
+ end
+ end
+ end
+ end
+
context 'when no emoji has been awarded by the current_user yet' do
# Create an award emoji for another user. This therefore tests that
# toggling is correctly scoped to the user's emoji only.
diff --git a/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb b/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb
index df64caa1cfb..8e71d77f7bc 100644
--- a/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb
+++ b/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb
@@ -131,22 +131,24 @@ RSpec.describe 'Reposition and move issue within board lists', feature_category:
end
def mutation(additional_params = {})
- graphql_mutation(mutation_name, issue_move_params.merge(additional_params),
- <<-QL.strip_heredoc
- clientMutationId
- issue {
- iid,
- relativePosition
- labels {
- edges {
- node{
- title
- }
- }
- }
- }
- errors
- QL
+ graphql_mutation(
+ mutation_name,
+ issue_move_params.merge(additional_params),
+ <<-QL.strip_heredoc
+ clientMutationId
+ issue {
+ iid,
+ relativePosition
+ labels {
+ edges {
+ node{
+ title
+ }
+ }
+ }
+ }
+ errors
+ QL
)
end
end
diff --git a/spec/requests/api/graphql/mutations/ci/catalog/resources/create_spec.rb b/spec/requests/api/graphql/mutations/ci/catalog/resources/create_spec.rb
new file mode 100644
index 00000000000..f990cab55f4
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ci/catalog/resources/create_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'CatalogResourcesCreate', feature_category: :pipeline_composition do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project, :catalog_resource_with_components) }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path
+ }
+ graphql_mutation(:catalog_resources_create, variables,
+ <<-QL.strip_heredoc
+ errors
+ QL
+ )
+ end
+
+ context 'when unauthorized' do
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when authorized' do
+ context 'with a valid project' do
+ before_all do
+ project.add_owner(current_user)
+ end
+
+ it 'creates a catalog resource' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_mutation_response(:catalog_resources_create)['errors']).to be_empty
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb b/spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb
new file mode 100644
index 00000000000..07465777263
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'CatalogResourceUnpublish', feature_category: :pipeline_composition do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be_with_reload(:resource) { create(:ci_catalog_resource) }
+
+ let(:mutation) do
+ graphql_mutation(
+ :catalog_resource_unpublish,
+ id: resource.to_gid.to_s
+ )
+ end
+
+ subject(:post_query) { post_graphql_mutation(mutation, current_user: current_user) }
+
+ context 'when unauthorized' do
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when authorized' do
+ before_all do
+ resource.project.add_owner(current_user)
+ end
+
+ context 'when the catalog resource is in published state' do
+ it 'updates the state to draft' do
+ resource.update!(state: :published)
+ expect(resource.state).to eq('published')
+
+ post_query
+
+ expect(resource.reload.state).to eq('draft')
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ context 'when the catalog resource is already in draft state' do
+ it 'leaves the state as draft' do
+ expect(resource.state).to eq('draft')
+
+ post_query
+
+ expect(resource.reload.state).to eq('draft')
+ expect_graphql_errors_to_be_empty
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb
index e7edc86bea0..70b154946ef 100644
--- a/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb
+++ b/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb
@@ -13,13 +13,15 @@ RSpec.describe 'PipelineRetry', feature_category: :continuous_integration do
variables = {
id: pipeline.to_global_id.to_s
}
- graphql_mutation(:pipeline_retry, variables,
- <<-QL
- errors
- pipeline {
- id
- }
- QL
+ graphql_mutation(
+ :pipeline_retry,
+ variables,
+ <<-QL
+ errors
+ pipeline {
+ id
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb b/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb
index ef0d44395bf..dd4b015409b 100644
--- a/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb
@@ -23,8 +23,8 @@ RSpec.describe 'Create a new cluster agent token', feature_category: :deployment
context 'without user permissions' do
it_behaves_like 'a mutation that returns top-level errors',
- errors: ["The resource that you are attempting to access does not exist "\
- "or you don't have permission to perform this action"]
+ errors: ["The resource that you are attempting to access does not exist "\
+ "or you don't have permission to perform this action"]
it 'does not create a token' do
expect { post_graphql_mutation(mutation, current_user: current_user) }.not_to change(Clusters::AgentToken, :count)
diff --git a/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb
index b70a6282a7a..a2a093d63e6 100644
--- a/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb
@@ -22,8 +22,8 @@ RSpec.describe 'Delete a cluster agent', feature_category: :deployment_managemen
context 'without project permissions' do
it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist '\
- 'or you don\'t have permission to perform this action']
+ errors: ['The resource that you are attempting to access does not exist '\
+ 'or you don\'t have permission to perform this action']
it 'does not delete cluster agent' do
expect { cluster_agent.reload }.not_to raise_error
diff --git a/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb b/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb
new file mode 100644
index 00000000000..0c708c3dc41
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb
@@ -0,0 +1,180 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Creating the container registry protection rule', :aggregate_failures, feature_category: :container_registry do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user, maintainer_projects: [project]) }
+
+ let(:container_registry_protection_rule_attributes) do
+ build_stubbed(:container_registry_protection_rule, project: project)
+ end
+
+ let(:kwargs) do
+ {
+ project_path: project.full_path,
+ container_path_pattern: container_registry_protection_rule_attributes.container_path_pattern,
+ push_protected_up_to_access_level: 'MAINTAINER',
+ delete_protected_up_to_access_level: 'MAINTAINER'
+ }
+ end
+
+ let(:mutation) do
+ graphql_mutation(:create_container_registry_protection_rule, kwargs,
+ <<~QUERY
+ containerRegistryProtectionRule {
+ id
+ containerPathPattern
+ }
+ clientMutationId
+ errors
+ QUERY
+ )
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:create_container_registry_protection_rule) }
+
+ subject { post_graphql_mutation(mutation, current_user: user) }
+
+ shared_examples 'a successful response' do
+ it { subject.tap { expect_graphql_errors_to_be_empty } }
+
+ it do
+ subject
+
+ expect(mutation_response).to include(
+ 'errors' => be_blank,
+ 'containerRegistryProtectionRule' => {
+ 'id' => be_present,
+ 'containerPathPattern' => kwargs[:container_path_pattern]
+ }
+ )
+ end
+
+ it 'creates container registry protection rule in the database' do
+ expect { subject }.to change { ::ContainerRegistry::Protection::Rule.count }.by(1)
+
+ expect(::ContainerRegistry::Protection::Rule.where(project: project,
+ container_path_pattern: kwargs[:container_path_pattern])).to exist
+ end
+ end
+
+ shared_examples 'an erroneous response' do
+ it { expect { subject }.not_to change { ::ContainerRegistry::Protection::Rule.count } }
+ end
+
+ it_behaves_like 'a successful response'
+
+ context 'with invalid input fields `pushProtectedUpToAccessLevel` and `deleteProtectedUpToAccessLevel`' do
+ let(:kwargs) do
+ super().merge(
+ push_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL',
+ delete_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL'
+ )
+ end
+
+ it_behaves_like 'an erroneous response'
+
+ it {
+ subject
+
+ expect_graphql_errors_to_include([/pushProtectedUpToAccessLevel/, /deleteProtectedUpToAccessLevel/])
+ }
+ end
+
+ context 'with invalid input field `containerPathPattern`' do
+ let(:kwargs) do
+ super().merge(container_path_pattern: '')
+ end
+
+ it_behaves_like 'an erroneous response'
+
+ it { subject.tap { expect_graphql_errors_to_be_empty } }
+
+ it {
+ subject.tap do
+ expect(mutation_response['errors']).to eq ["Container path pattern can't be blank"]
+ end
+ }
+ end
+
+ context 'with existing containers protection rule' do
+ let_it_be(:existing_container_registry_protection_rule) do
+ create(:container_registry_protection_rule, project: project,
+ push_protected_up_to_access_level: Gitlab::Access::DEVELOPER)
+ end
+
+ context 'when container name pattern is slightly different' do
+ let(:kwargs) do
+ # The field `container_path_pattern` is unique; this is why we change the value in a minimum way
+ super().merge(
+ container_path_pattern: "#{existing_container_registry_protection_rule.container_path_pattern}-unique"
+ )
+ end
+
+ it_behaves_like 'a successful response'
+
+ it 'adds another container registry protection rule to the database' do
+ expect { subject }.to change { ::ContainerRegistry::Protection::Rule.count }.from(1).to(2)
+ end
+ end
+
+ context 'when field `container_path_pattern` is taken' do
+ let(:kwargs) do
+ super().merge(container_path_pattern: existing_container_registry_protection_rule.container_path_pattern,
+ push_protected_up_to_access_level: 'MAINTAINER')
+ end
+
+ it_behaves_like 'an erroneous response'
+
+ it { subject.tap { expect_graphql_errors_to_be_empty } }
+
+ it 'returns without error' do
+ subject
+
+ expect(mutation_response['errors']).to eq ['Container path pattern has already been taken']
+ end
+
+ it 'does not create new container protection rules' do
+ expect(::ContainerRegistry::Protection::Rule.where(project: project,
+ container_path_pattern: kwargs[:container_path_pattern],
+ push_protected_up_to_access_level: Gitlab::Access::MAINTAINER)).not_to exist
+ end
+ end
+ end
+
+ context 'when user does not have permission' do
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+ let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
+ let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
+ let_it_be(:anonymous) { create(:user) }
+
+ where(:user) do
+ [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)]
+ end
+
+ with_them do
+ it_behaves_like 'an erroneous response'
+
+ it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } }
+ end
+ end
+
+ context "when feature flag ':container_registry_protected_containers' disabled" do
+ before do
+ stub_feature_flags(container_registry_protected_containers: false)
+ end
+
+ it_behaves_like 'an erroneous response'
+
+ it { subject.tap { expect(::ContainerRegistry::Protection::Rule.where(project: project)).not_to exist } }
+
+ it 'returns error of disabled feature flag' do
+ subject.tap do
+ expect_graphql_errors_to_include(/'container_registry_protected_containers' feature flag is disabled/)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb b/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb
index 0cb607e13ec..7ced22890df 100644
--- a/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb
+++ b/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe 'Destroying a container repository tags', feature_category: :cont
shared_examples 'destroying the container repository tags' do
before do
stub_delete_reference_requests(tags)
- expect_delete_tag_by_names(tags)
+ expect_delete_tags(tags)
allow_next_instance_of(ContainerRegistry::Client) do |client|
allow(client).to receive(:supports_tag_delete?).and_return(true)
end
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 7ea32ae6d19..6f421abc489 100644
--- a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
@@ -47,28 +47,28 @@ RSpec.describe "deleting designs", feature_category: :design_management do
context 'the designs list is empty' do
it_behaves_like 'a failed request' do
let(:designs) { [] }
- let(:the_error) { a_string_matching %r/no filenames/ }
+ let(:the_error) { a_string_matching %r{no filenames} }
end
end
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| double('file', filename: fn) } }
- let(:the_error) { a_string_matching %r/filenames were not found/ }
+ 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
context 'the current user does not have developer access' do
it_behaves_like 'a failed request' do
let(:current_user) { create(:user) }
- let(:the_error) { a_string_matching %r/you don't have permission/ }
+ let(:the_error) { a_string_matching %r{you don't have permission} }
end
end
context "when the issue does not exist" do
it_behaves_like 'a failed request' do
let(:variables) { { iid: "1234567890" } }
- let(:the_error) { a_string_matching %r/does not exist/ }
+ let(:the_error) { a_string_matching %r{does not exist} }
end
end
diff --git a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
index 9b42b32c150..82a88a2c593 100644
--- a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
@@ -36,10 +36,11 @@ RSpec.describe "uploading designs", feature_category: :design_management do
end
it 'returns an error' do
- workhorse_post_with_file(api('/', current_user, version: 'graphql'),
- params: params,
- file_key: '1'
- )
+ workhorse_post_with_file(
+ api('/', current_user, version: 'graphql'),
+ params: params,
+ file_key: '1'
+ )
expect(response).to have_attributes(
code: eq('400'),
diff --git a/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb b/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb
index 85e21952f47..df6c20d6176 100644
--- a/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb
@@ -19,19 +19,21 @@ RSpec.describe 'Link alerts to an incident', feature_category: :incident_managem
alert_references: [alert1.to_reference, alert2.details_url]
}
- graphql_mutation(:issue_link_alerts, variables,
- <<-QL.strip_heredoc
- clientMutationId
- errors
- issue {
- iid
- alertManagementAlerts {
- nodes {
- iid
- }
- }
- }
- QL
+ graphql_mutation(
+ :issue_link_alerts,
+ variables,
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ alertManagementAlerts {
+ nodes {
+ iid
+ }
+ }
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/issues/move_spec.rb b/spec/requests/api/graphql/mutations/issues/move_spec.rb
index 7d9579067b6..24188d5341d 100644
--- a/spec/requests/api/graphql/mutations/issues/move_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/move_spec.rb
@@ -16,14 +16,16 @@ RSpec.describe 'Moving an issue', feature_category: :team_planning do
iid: issue.iid.to_s
}
- graphql_mutation(:issue_move, variables,
- <<-QL.strip_heredoc
- clientMutationId
- errors
- issue {
- title
- }
- QL
+ graphql_mutation(
+ :issue_move,
+ variables,
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ title
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
index c5e6901d8f8..c62995c0b9b 100644
--- a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
@@ -15,15 +15,17 @@ RSpec.describe 'Setting an issue as confidential', feature_category: :team_plann
project_path: project.full_path,
iid: issue.iid.to_s
}
- graphql_mutation(:issue_set_confidential, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- issue {
- iid
- confidential
- }
- QL
+ graphql_mutation(
+ :issue_set_confidential,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ confidential
+ }
+ QL
)
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 497ae1cc13f..cdab267162e 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
@@ -26,18 +26,20 @@ RSpec.describe 'Setting issues crm contacts', feature_category: :service_desk do
contact_ids: contact_ids
}
- graphql_mutation(:issue_set_crm_contacts, variables,
- <<-QL.strip_heredoc
- clientMutationId
- errors
- issue {
- customerRelationsContacts {
- nodes {
- id
- }
- }
- }
- QL
+ graphql_mutation(
+ :issue_set_crm_contacts,
+ variables,
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ customerRelationsContacts {
+ nodes {
+ id
+ }
+ }
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
index 1a5a64e4196..f7c5febe56f 100644
--- a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
@@ -15,15 +15,17 @@ RSpec.describe 'Setting Due Date of an issue', feature_category: :team_planning
project_path: project.full_path,
iid: issue.iid.to_s
}
- graphql_mutation(:issue_set_due_date, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- issue {
- iid
- dueDate
- }
- QL
+ graphql_mutation(
+ :issue_set_due_date,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ dueDate
+ }
+ QL
)
end
@@ -68,7 +70,7 @@ RSpec.describe 'Setting Due Date of an issue', feature_category: :team_planning
it 'returns an error' do
post_graphql_mutation(mutation, current_user: current_user)
- expect(graphql_errors).to include(a_hash_including('message' => /Arguments must be provided: dueDate/))
+ expect(graphql_errors).to include(a_hash_including('message' => 'issueSetDueDate has the wrong arguments'))
end
end
diff --git a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
index a8025894b1e..547ec280150 100644
--- a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
@@ -16,15 +16,17 @@ RSpec.describe 'Setting an issue as locked', feature_category: :team_planning do
project_path: project.full_path,
iid: issue.iid.to_s
}
- graphql_mutation(:issue_set_locked, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- issue {
- iid
- discussionLocked
- }
- QL
+ graphql_mutation(
+ :issue_set_locked,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ discussionLocked
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
index 77262c7f64f..d53b938a983 100644
--- a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
@@ -17,15 +17,17 @@ RSpec.describe 'Setting severity level of an incident', feature_category: :incid
iid: incident.iid.to_s
}
- graphql_mutation(:issue_set_severity, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- issue {
- iid
- severity
- }
- QL
+ graphql_mutation(
+ :issue_set_severity,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ severity
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb b/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb
index 7f6f968b1dd..807afdfb812 100644
--- a/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb
@@ -21,19 +21,21 @@ RSpec.describe 'Unlink alert from an incident', feature_category: :incident_mana
alert_id: alert_to_unlink.to_global_id.to_s
}
- graphql_mutation(:issue_unlink_alert, variables,
- <<-QL.strip_heredoc
- clientMutationId
- errors
- issue {
- iid
- alertManagementAlerts {
- nodes {
- id
- }
- }
- }
- QL
+ graphql_mutation(
+ :issue_unlink_alert,
+ variables,
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ alertManagementAlerts {
+ nodes {
+ id
+ }
+ }
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb
index 7a1b3982111..ec82941b094 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb
@@ -16,11 +16,13 @@ RSpec.describe 'Setting assignees of a merge request', feature_category: :code_r
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_reviewer_rereview, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- QL
+ graphql_mutation(
+ :merge_request_reviewer_rereview,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
index 4a7d1083f2e..cb7bac771b3 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -21,19 +21,21 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_set_assignees, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- mergeRequest {
- id
- assignees {
- nodes {
- username
- }
- }
- }
- QL
+ graphql_mutation(
+ :merge_request_set_assignees,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ mergeRequest {
+ id
+ assignees {
+ nodes {
+ username
+ }
+ }
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb
index 0c2e2975350..a2c5c235d25 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb
@@ -15,15 +15,17 @@ RSpec.describe 'Setting Draft status of a merge request', feature_category: :cod
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_set_draft, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- mergeRequest {
- id
- title
- }
- QL
+ graphql_mutation(
+ :merge_request_set_draft,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ mergeRequest {
+ id
+ title
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
index e40a3cf7ce9..4ddd10b1734 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb
@@ -17,19 +17,21 @@ RSpec.describe 'Setting labels of a merge request' do
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_set_labels, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- mergeRequest {
- id
- labels {
- nodes {
- id
- }
- }
- }
- QL
+ graphql_mutation(
+ :merge_request_set_labels,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ mergeRequest {
+ id
+ labels {
+ nodes {
+ id
+ }
+ }
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
index 73a38adf723..a6ddb9beb5c 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb
@@ -15,15 +15,17 @@ RSpec.describe 'Setting locked status of a merge request', feature_category: :co
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_set_locked, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- mergeRequest {
- id
- discussionLocked
- }
- QL
+ graphql_mutation(
+ :merge_request_set_locked,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ mergeRequest {
+ id
+ discussionLocked
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
index 1898ee5a62d..9debfbd474b 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb
@@ -16,17 +16,19 @@ RSpec.describe 'Setting milestone of a merge request', feature_category: :code_r
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_set_milestone, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- mergeRequest {
- id
- milestone {
- id
- }
- }
- QL
+ graphql_mutation(
+ :merge_request_set_milestone,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ mergeRequest {
+ id
+ milestone {
+ id
+ }
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb
index fd87112be33..c9efba689c2 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb
@@ -21,19 +21,21 @@ RSpec.describe 'Setting reviewers of a merge request', :assume_throttled, featur
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_set_reviewers, variables.merge(input),
- <<-QL.strip_heredoc
- clientMutationId
- errors
- mergeRequest {
- id
- reviewers {
- nodes {
- username
- }
- }
- }
- QL
+ graphql_mutation(
+ :merge_request_set_reviewers,
+ variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ mergeRequest {
+ id
+ reviewers {
+ nodes {
+ username
+ }
+ }
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_time_estimate_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_time_estimate_spec.rb
index 6bc130a97cf..541cdf0660d 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_time_estimate_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_time_estimate_spec.rb
@@ -17,8 +17,11 @@ RSpec.describe 'Setting time estimate of a merge request', feature_category: :co
let(:extra_params) { { project_path: project.full_path } }
let(:input_params) { input.merge(extra_params) }
- let(:mutation) { graphql_mutation(:merge_request_update, input_params, nil, ['productAnalyticsState']) }
let(:mutation_response) { graphql_mutation_response(:merge_request_update) }
+ let(:mutation) do
+ # exclude codequalityReportsComparer because it's behind a feature flag
+ graphql_mutation(:merge_request_update, input_params, nil, %w[productAnalyticsState codequalityReportsComparer])
+ end
context 'when the user is not allowed to update a merge request' do
before_all do
diff --git a/spec/requests/api/graphql/mutations/merge_requests/update_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/update_spec.rb
index 48db23569b6..ef21f77d818 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/update_spec.rb
@@ -12,8 +12,11 @@ RSpec.describe 'Update of an existing merge request', feature_category: :code_re
let(:input) { { 'iid' => merge_request.iid.to_s } }
let(:extra_params) { { project_path: project.full_path } }
let(:input_params) { input.merge(extra_params) }
- let(:mutation) { graphql_mutation(:merge_request_update, input_params, nil, ['productAnalyticsState']) }
let(:mutation_response) { graphql_mutation_response(:merge_request_update) }
+ let(:mutation) do
+ # exclude codequalityReportsComparer because it's behind a feature flag
+ graphql_mutation(:merge_request_update, input_params, nil, %w[productAnalyticsState codequalityReportsComparer])
+ end
context 'when the user is not allowed to update the merge request' do
it_behaves_like 'a mutation that returns a top-level access error'
@@ -28,5 +31,17 @@ RSpec.describe 'Update of an existing merge request', feature_category: :code_re
let(:resource) { merge_request }
let(:mutation_name) { 'mergeRequestUpdate' }
end
+
+ context 'when required arguments are missing' do
+ let(:input_params) { {} }
+
+ it_behaves_like 'a mutation that returns top-level errors' do
+ let(:match_errors) do
+ include(end_with(
+ 'invalid value for projectPath (Expected value to not be null), iid (Expected value to not be null)'
+ ))
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb
index 480e184a60c..738dc3078e7 100644
--- a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb
@@ -129,26 +129,6 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
it_behaves_like 'returning a success'
it_behaves_like 'rejecting invalid regex'
-
- context 'when nuget_duplicates_option FF is disabled' do
- let(:params) do
- {
- namespace_path: namespace.full_path,
- 'nugetDuplicatesAllowed' => false
- }
- end
-
- before do
- stub_feature_flags(nuget_duplicates_option: false)
- end
-
- it 'raises an error', :aggregate_failures do
- subject
-
- expect(graphql_errors.size).to eq(1)
- expect(graphql_errors.first['message']).to include('feature flag is disabled')
- end
- end
end
RSpec.shared_examples 'accepting the mutation request creating the package settings' do
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
index 37bcdf61d23..33d840cafd7 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -5,13 +5,15 @@ require 'spec_helper'
RSpec.describe 'Adding a Note', feature_category: :team_planning do
include GraphqlHelpers
- let_it_be(:current_user) { create(:user) }
-
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group).tap { |g| g.add_developer(developer) } }
+ let_it_be_with_reload(:project) { create(:project, :repository, group: group) }
+ let_it_be(:developer) { create(:user).tap { |u| group.add_developer(u) } }
let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
- let(:project) { create(:project, :repository) }
let(:discussion) { nil }
let(:head_sha) { nil }
let(:body) { 'Body text' }
+ let(:current_user) { user }
let(:mutation) do
variables = {
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
@@ -30,9 +32,7 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
it_behaves_like 'a Note mutation when the user does not have permission'
context 'when the user has permission' do
- before do
- project.add_developer(current_user)
- end
+ let(:current_user) { developer }
it_behaves_like 'a working GraphQL mutation'
@@ -78,8 +78,10 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
context 'for an issue' do
- let(:noteable) { create(:issue, project: project) }
+ let_it_be_with_reload(:issue) { create(:issue, project: project) }
+ let(:noteable) { issue }
let(:mutation) { graphql_mutation(:create_note, variables) }
+ let(:variables_extra) { {} }
let(:variables) do
{
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
@@ -87,10 +89,6 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
}.merge(variables_extra)
end
- before do
- project.add_developer(current_user)
- end
-
context 'when using internal param' do
let(:variables_extra) { { internal: true } }
@@ -104,8 +102,8 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
context 'as work item' do
- let_it_be(:project) { create(:project) }
- let_it_be(:noteable) { create(:work_item, project: project) }
+ let_it_be_with_reload(:work_item) { create(:work_item, :task, project: project) }
+ let(:noteable) { work_item }
context 'when using internal param' do
let(:variables_extra) { { internal: true } }
@@ -120,10 +118,8 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
context 'without notes widget' do
- let(:variables_extra) { {} }
-
before do
- WorkItems::Type.default_by_type(:issue).widget_definitions.find_by_widget_type(:notes)
+ WorkItems::Type.default_by_type(:task).widget_definitions.find_by_widget_type(:notes)
.update!(disabled: true)
end
@@ -133,10 +129,6 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
end
context 'when body contains quick actions' do
- let_it_be(:noteable) { create(:work_item, :task, project: project) }
-
- let(:variables_extra) { {} }
-
it_behaves_like 'work item supports labels widget updates via quick actions'
it_behaves_like 'work item does not support labels widget updates via quick actions'
it_behaves_like 'work item supports assignee widget updates via quick actions'
@@ -145,6 +137,13 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
it_behaves_like 'work item does not support start and due date widget updates via quick actions'
it_behaves_like 'work item supports type change via quick actions'
end
+
+ context 'when work item is directly associated with a group' do
+ let_it_be_with_reload(:group_work_item) { create(:work_item, :group_level, :task, namespace: group) }
+ let(:noteable) { group_work_item }
+
+ it_behaves_like 'a Note mutation that creates a Note'
+ end
end
end
@@ -152,10 +151,6 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
let(:head_sha) { noteable.diff_head_sha }
let(:body) { '/merge' }
- before do
- project.add_developer(current_user)
- end
-
# NOTE: Known issue https://gitlab.com/gitlab-org/gitlab/-/issues/346557
it 'returns a nil note and info about the command in errors' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
index a5cd3c8b019..3f071a6d987 100644
--- a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
@@ -37,11 +37,13 @@ RSpec.describe 'Updating an image DiffNote', feature_category: :team_planning do
end
let!(:diff_note) do
- create(:image_diff_note_on_merge_request,
- noteable: noteable,
- project: noteable.project,
- note: original_body,
- position: original_position)
+ create(
+ :image_diff_note_on_merge_request,
+ noteable: noteable,
+ project: noteable.project,
+ note: original_body,
+ position: original_position
+ )
end
let(:mutation) do
diff --git a/spec/requests/api/graphql/mutations/organizations/create_spec.rb b/spec/requests/api/graphql/mutations/organizations/create_spec.rb
new file mode 100644
index 00000000000..ac6b04104ba
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/organizations/create_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Organizations::Create, feature_category: :cell do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+
+ let(:mutation) { graphql_mutation(:organization_create, params) }
+ let(:name) { 'Name' }
+ let(:path) { 'path' }
+ let(:params) do
+ {
+ name: name,
+ path: path
+ }
+ end
+
+ subject(:create_organization) { post_graphql_mutation(mutation, current_user: current_user) }
+
+ it { expect(described_class).to require_graphql_authorizations(:create_organization) }
+
+ def mutation_response
+ graphql_mutation_response(:organization_create)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { nil }
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+
+ it 'does not create an organization' do
+ expect { create_organization }.not_to change { Organizations::Organization.count }
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { user }
+
+ context 'when the params are invalid' do
+ let(:name) { '' }
+
+ it 'returns the validation error' do
+ create_organization
+
+ expect(mutation_response).to include('errors' => ["Name can't be blank"])
+ end
+ end
+
+ it 'creates an organization' do
+ expect { create_organization }.to change { Organizations::Organization.count }.by(1)
+ end
+
+ it 'returns the new organization' do
+ create_organization
+
+ expect(graphql_data_at(:organization_create, :organization)).to match a_hash_including(
+ 'name' => name,
+ 'path' => path
+ )
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb b/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb
index 2540e06be9a..5843109f356 100644
--- a/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb
@@ -17,14 +17,16 @@ RSpec.describe 'Updating the packages cleanup policy', feature_category: :packag
end
let(:mutation) do
- graphql_mutation(:update_packages_cleanup_policy, params,
- <<~QUERY
- packagesCleanupPolicy {
- keepNDuplicatedPackageFiles
- nextRunAt
- }
- errors
- QUERY
+ graphql_mutation(
+ :update_packages_cleanup_policy,
+ params,
+ <<~QUERY
+ packagesCleanupPolicy {
+ keepNDuplicatedPackageFiles
+ nextRunAt
+ }
+ errors
+ QUERY
)
end
diff --git a/spec/requests/api/graphql/mutations/packages/protection/rule/create_spec.rb b/spec/requests/api/graphql/mutations/packages/protection/rule/create_spec.rb
index b0c8526fa1c..ae5b6a5af95 100644
--- a/spec/requests/api/graphql/mutations/packages/protection/rule/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/packages/protection/rule/create_spec.rb
@@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe 'Creating the packages protection rule', :aggregate_failures, feature_category: :package_registry do
include GraphqlHelpers
- using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, maintainer_projects: [project]) }
@@ -15,151 +14,162 @@ RSpec.describe 'Creating the packages protection rule', :aggregate_failures, fea
{
project_path: project.full_path,
package_name_pattern: package_protection_rule_attributes.package_name_pattern,
- package_type: "NPM",
- push_protected_up_to_access_level: "MAINTAINER"
+ package_type: 'NPM',
+ push_protected_up_to_access_level: 'MAINTAINER'
}
end
let(:mutation) do
graphql_mutation(:create_packages_protection_rule, kwargs,
<<~QUERY
- clientMutationId
+ packageProtectionRule {
+ id
+ packageNamePattern
+ packageType
+ pushProtectedUpToAccessLevel
+ }
errors
QUERY
)
end
- let(:mutation_response) { graphql_mutation_response(:create_packages_protection_rule) }
+ let(:mutation_response_package_protection_rule) do
+ graphql_data_at(:createPackagesProtectionRule, :packageProtectionRule)
+ end
- describe 'post graphql mutation' do
- subject { post_graphql_mutation(mutation, current_user: user) }
+ let(:mutation_response_errors) { graphql_data_at(:createPackagesProtectionRule, :errors) }
- context 'without existing packages protection rule' do
- it 'returns without error' do
- subject
+ subject { post_graphql_mutation(mutation, current_user: user) }
- expect_graphql_errors_to_be_empty
- end
+ shared_examples 'a successful response' do
+ it 'returns without error' do
+ subject
- it 'returns the created packages protection rule' do
- expect { subject }.to change { ::Packages::Protection::Rule.count }.by(1)
+ expect_graphql_errors_to_be_empty
+ expect(mutation_response_errors).to be_empty
+ end
- expect_graphql_errors_to_be_empty
- expect(Packages::Protection::Rule.where(project: project).count).to eq 1
+ it 'returns the created packages protection rule' do
+ subject
- expect(Packages::Protection::Rule.where(project: project,
- package_name_pattern: kwargs[:package_name_pattern])).to exist
- end
+ expect(mutation_response_package_protection_rule).to include(
+ 'id' => be_present,
+ 'packageNamePattern' => kwargs[:package_name_pattern],
+ 'packageType' => kwargs[:package_type],
+ 'pushProtectedUpToAccessLevel' => kwargs[:push_protected_up_to_access_level]
+ )
+ end
- context 'when invalid fields are given' do
- let(:kwargs) do
- {
- project_path: project.full_path,
- package_name_pattern: '',
- package_type: 'UNKNOWN_PACKAGE_TYPE',
- push_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL'
- }
- end
-
- it 'returns error about required argument' do
- subject
-
- expect_graphql_errors_to_include(/was provided invalid value for packageType/)
- expect_graphql_errors_to_include(/pushProtectedUpToAccessLevel/)
- end
- end
+ it 'creates one package protection rule' do
+ expect { subject }.to change { ::Packages::Protection::Rule.count }.by(1)
+
+ expect(Packages::Protection::Rule.last).to have_attributes(
+ project: project,
+ package_name_pattern: kwargs[:package_name_pattern],
+ package_type: kwargs[:package_type].downcase,
+ push_protected_up_to_access_level: kwargs[:push_protected_up_to_access_level].downcase
+ )
end
+ end
- context 'when user does not have permission' do
- let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
- let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
- let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
- let_it_be(:anonymous) { create(:user) }
+ shared_examples 'an erroneous response' do
+ it 'does not create one package protection rule' do
+ expect { subject }.not_to change { ::Packages::Protection::Rule.count }
+ end
+ end
- where(:user) do
- [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)]
- end
+ it_behaves_like 'a successful response'
- with_them do
- it 'returns an error' do
- expect { subject }.not_to change { ::Packages::Protection::Rule.count }
+ context 'with invalid kwargs leading to error from graphql' do
+ let(:kwargs) do
+ super().merge!(
+ package_name_pattern: '',
+ package_type: 'UNKNOWN_PACKAGE_TYPE',
+ push_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL'
+ )
+ end
- expect_graphql_errors_to_include(/you don't have permission to perform this action/)
- end
- end
+ it_behaves_like 'an erroneous response'
+
+ it 'returns error about required argument' do
+ subject
+
+ expect_graphql_errors_to_include(/was provided invalid value for packageType/)
+ expect_graphql_errors_to_include(/pushProtectedUpToAccessLevel/)
end
+ end
- context 'with existing packages protection rule' do
- let_it_be(:existing_package_protection_rule) do
- create(:package_protection_rule, project: project, push_protected_up_to_access_level: Gitlab::Access::DEVELOPER)
- end
+ context 'with invalid kwargs leading to error from business model' do
+ let(:kwargs) { super().merge!(package_name_pattern: '') }
- context 'when package name pattern is slightly different' do
- let(:kwargs) do
- {
- project_path: project.full_path,
- # The field `package_name_pattern` is unique; this is why we change the value in a minimum way
- package_name_pattern: "#{existing_package_protection_rule.package_name_pattern}-unique",
- package_type: "NPM",
- push_protected_up_to_access_level: "DEVELOPER"
- }
- end
-
- it 'returns the created packages protection rule' do
- expect { subject }.to change { ::Packages::Protection::Rule.count }.by(1)
-
- expect(Packages::Protection::Rule.where(project: project).count).to eq 2
- expect(Packages::Protection::Rule.where(project: project,
- package_name_pattern: kwargs[:package_name_pattern])).to exist
- end
-
- it 'returns without error' do
- subject
-
- expect_graphql_errors_to_be_empty
- end
- end
+ it_behaves_like 'an erroneous response'
- context 'when field `package_name_pattern` is taken' do
- let(:kwargs) do
- {
- project_path: project.full_path,
- package_name_pattern: existing_package_protection_rule.package_name_pattern,
- package_type: 'NPM',
- push_protected_up_to_access_level: 'MAINTAINER'
- }
- end
-
- it 'returns without error' do
- subject
-
- expect(mutation_response).to include 'errors' => ['Package name pattern has already been taken']
- end
-
- it 'does not create new package protection rules' do
- expect { subject }.to change { Packages::Protection::Rule.count }.by(0)
-
- expect(Packages::Protection::Rule.where(project: project,
- package_name_pattern: kwargs[:package_name_pattern],
- push_protected_up_to_access_level: Gitlab::Access::MAINTAINER)).not_to exist
- end
- end
+ it 'returns an error' do
+ subject.tap { expect(mutation_response_errors).to include(/Package name pattern can't be blank/) }
end
+ end
- context "when feature flag ':packages_protected_packages' disabled" do
- before do
- stub_feature_flags(packages_protected_packages: false)
+ context 'with existing packages protection rule' do
+ let_it_be(:existing_package_protection_rule) do
+ create(:package_protection_rule, project: project, push_protected_up_to_access_level: :maintainer)
+ end
+
+ let(:kwargs) { super().merge!(package_name_pattern: existing_package_protection_rule.package_name_pattern) }
+
+ it_behaves_like 'an erroneous response'
+
+ it 'returns an error' do
+ subject.tap { expect(mutation_response_errors).to include(/Package name pattern has already been taken/) }
+ end
+
+ context 'when field `package_name_pattern` is different than existing one' do
+ let(:kwargs) do
+ # The field `package_name_pattern` is unique; this is why we change the value in a minimum way
+ super().merge!(package_name_pattern: "#{existing_package_protection_rule.package_name_pattern}-unique")
end
- it 'does not create any package protection rules' do
- expect { subject }.to change { Packages::Protection::Rule.count }.by(0)
+ it_behaves_like 'a successful response'
+ end
+
+ context 'when field `push_protected_up_to_access_level` is different than existing one' do
+ let(:kwargs) { super().merge!(push_protected_up_to_access_level: 'DEVELOPER') }
+
+ it_behaves_like 'an erroneous response'
- expect(Packages::Protection::Rule.where(project: project)).not_to exist
+ it 'returns an error' do
+ subject.tap { expect(mutation_response_errors).to include(/Package name pattern has already been taken/) }
end
+ end
+ end
- it 'returns error of disabled feature flag' do
- subject.tap { expect_graphql_errors_to_include(/'packages_protected_packages' feature flag is disabled/) }
+ context 'when user does not have permission' do
+ let_it_be(:anonymous) { create(:user) }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+ let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
+ let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
+
+ where(:user) do
+ [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)]
+ end
+
+ with_them do
+ it_behaves_like 'an erroneous response'
+
+ it 'returns an error' do
+ subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) }
end
end
end
+
+ context "when feature flag ':packages_protected_packages' disabled" do
+ before do
+ stub_feature_flags(packages_protected_packages: false)
+ end
+
+ it_behaves_like 'an erroneous response'
+
+ it 'returns error of disabled feature flag' do
+ subject.tap { expect_graphql_errors_to_include(/'packages_protected_packages' feature flag is disabled/) }
+ end
+ end
end
diff --git a/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb b/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb
new file mode 100644
index 00000000000..1d94d520674
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Deleting a package protection rule', :aggregate_failures, feature_category: :package_registry do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be_with_refind(:package_protection_rule) { create(:package_protection_rule, project: project) }
+ let_it_be(:current_user) { create(:user, maintainer_projects: [project]) }
+
+ let(:mutation) { graphql_mutation(:delete_packages_protection_rule, input) }
+ let(:mutation_response) { graphql_mutation_response(:delete_packages_protection_rule) }
+ let(:input) { { id: package_protection_rule.to_global_id } }
+
+ subject { post_graphql_mutation(mutation, current_user: current_user) }
+
+ shared_examples 'an erroneous reponse' do
+ it { subject.tap { expect(mutation_response).to be_blank } }
+ it { expect { subject }.not_to change { ::Packages::Protection::Rule.count } }
+ end
+
+ it_behaves_like 'a working GraphQL mutation'
+
+ it 'responds with deleted package protection rule' do
+ subject
+
+ expect(mutation_response).to include(
+ 'errors' => be_blank,
+ 'packageProtectionRule' => {
+ 'id' => package_protection_rule.to_global_id.to_s,
+ 'packageNamePattern' => package_protection_rule.package_name_pattern,
+ 'packageType' => package_protection_rule.package_type.upcase,
+ 'pushProtectedUpToAccessLevel' => package_protection_rule.push_protected_up_to_access_level.upcase
+ }
+ )
+ end
+
+ it { is_expected.tap { expect_graphql_errors_to_be_empty } }
+ it { expect { subject }.to change { ::Packages::Protection::Rule.count }.from(1).to(0) }
+
+ context 'with existing package protection rule belonging to other project' do
+ let_it_be(:package_protection_rule) do
+ create(:package_protection_rule, package_name_pattern: 'protection_rule_other_project')
+ end
+
+ it_behaves_like 'an erroneous reponse'
+
+ it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } }
+ end
+
+ context 'with deleted package protection rule' do
+ let!(:package_protection_rule) do
+ create(:package_protection_rule, project: project, package_name_pattern: 'protection_rule_deleted').destroy!
+ end
+
+ it_behaves_like 'an erroneous reponse'
+
+ it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } }
+ end
+
+ context 'when current_user does not have permission' do
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+ let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
+ let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
+ let_it_be(:anonymous) { create(:user) }
+
+ where(:current_user) do
+ [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)]
+ end
+
+ with_them do
+ it_behaves_like 'an erroneous reponse'
+
+ it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } }
+ end
+ end
+
+ context "when feature flag ':packages_protected_packages' disabled" do
+ before do
+ stub_feature_flags(packages_protected_packages: false)
+ end
+
+ it_behaves_like 'an erroneous reponse'
+
+ it { subject.tap { expect_graphql_errors_to_include(/'packages_protected_packages' feature flag is disabled/) } }
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb b/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb
index 45028cba3ae..fdd4de865ad 100644
--- a/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb
@@ -10,12 +10,14 @@ RSpec.describe 'Updating an existing release asset link', feature_category: :rel
let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
let_it_be(:release_link) do
- create(:release_link,
- release: release,
- name: 'link name',
- url: 'https://example.com/url',
- filepath: '/permanent/path',
- link_type: 'package')
+ create(
+ :release_link,
+ release: release,
+ name: 'link name',
+ url: 'https://example.com/url',
+ filepath: '/permanent/path',
+ link_type: 'package'
+ )
end
let(:current_user) { developer }
diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
index a6d727ae6d3..7094cb807b2 100644
--- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d
let(:current_user) { nil }
it_behaves_like 'a mutation that returns top-level errors',
- errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+ errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
it 'does not create the Snippet' do
expect do
@@ -122,7 +122,7 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d
let(:project_path) { 'foobar' }
it_behaves_like 'a mutation that returns top-level errors',
- errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+ errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
context 'when the feature is disabled' do
@@ -131,7 +131,7 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d
end
it_behaves_like 'a mutation that returns top-level errors',
- errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+ errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
it_behaves_like 'snippet edit usage data counters'
@@ -169,8 +169,8 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d
end
it_behaves_like 'expected files argument', nil, nil
- it_behaves_like 'expected files argument', %w(foo bar), %w(foo bar)
- it_behaves_like 'expected files argument', 'foo', %w(foo)
+ it_behaves_like 'expected files argument', %w[foo bar], %w[foo bar]
+ it_behaves_like 'expected files argument', 'foo', %w[foo]
context 'when files has an invalid value' do
let(:uploaded_files) { [1] }
diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
index c3f818b6627..7b0de7a9fba 100644
--- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe 'Destroying a Snippet', feature_category: :source_code_management
let(:current_user) { create(:user) }
it_behaves_like 'a mutation that returns top-level errors',
- errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+ errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
it 'does not destroy the Snippet' do
expect do
@@ -53,7 +53,7 @@ RSpec.describe 'Destroying a Snippet', feature_category: :source_code_management
let!(:snippet_gid) { project.to_gid.to_s }
it 'returns an error' do
- err_message = %["#{snippet_gid}" does not represent an instance of Snippet]
+ err_message = %("#{snippet_gid}" does not represent an instance of Snippet)
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
index 9a8c027da8a..6fd41437ce4 100644
--- a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe 'Mark snippet as spam', feature_category: :source_code_management
let(:current_user) { other_user }
it_behaves_like 'a mutation that returns top-level errors',
- errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+ errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
it_behaves_like 'does not mark the snippet as spam'
end
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index 78df78cb2a0..0bc475c7105 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d
let(:current_user) { create(:user) }
it_behaves_like 'a mutation that returns top-level errors',
- errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+ errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
it 'does not update the Snippet' do
expect do
@@ -118,13 +118,15 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d
describe 'PersonalSnippet' do
let(:snippet) do
- create(:personal_snippet,
- :private,
- :repository,
- file_name: original_file_name,
- title: original_title,
- content: original_content,
- description: original_description)
+ create(
+ :personal_snippet,
+ :private,
+ :repository,
+ file_name: original_file_name,
+ title: original_title,
+ content: original_content,
+ description: original_description
+ )
end
it_behaves_like 'graphql update actions'
@@ -139,15 +141,17 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d
let_it_be(:project) { create(:project, :private) }
let(:snippet) do
- create(:project_snippet,
- :private,
- :repository,
- project: project,
- author: create(:user),
- file_name: original_file_name,
- title: original_title,
- content: original_content,
- description: original_description)
+ create(
+ :project_snippet,
+ :private,
+ :repository,
+ project: project,
+ author: create(:user),
+ file_name: original_file_name,
+ title: original_title,
+ content: original_content,
+ description: original_description
+ )
end
context 'when the author is not a member of the project' do
diff --git a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
index c611c6ee2a1..429aa06d9f1 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
@@ -21,12 +21,14 @@ RSpec.describe 'Marking all todos done', feature_category: :team_planning do
let(:input) { {} }
let(:mutation) do
- graphql_mutation(:todos_mark_all_done, input,
- <<-QL.strip_heredoc
- clientMutationId
- todos { id }
- errors
- QL
+ graphql_mutation(
+ :todos_mark_all_done,
+ input,
+ <<-QL.strip_heredoc
+ clientMutationId
+ todos { id }
+ errors
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
index 60700d8024c..c09f89ef567 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
@@ -19,15 +19,17 @@ RSpec.describe 'Marking todos done', feature_category: :team_planning do
let(:input) { { id: todo1.to_global_id.to_s } }
let(:mutation) do
- graphql_mutation(:todo_mark_done, input,
- <<-QL.strip_heredoc
- clientMutationId
- errors
- todo {
- id
- state
- }
- QL
+ graphql_mutation(
+ :todo_mark_done,
+ input,
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ todo {
+ id
+ state
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb
index 9daa243cf8e..4bbfc7b2f1d 100644
--- a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb
@@ -20,15 +20,17 @@ RSpec.describe 'Restoring many Todos', feature_category: :team_planning do
let(:input) { { ids: input_ids } }
let(:mutation) do
- graphql_mutation(:todo_restore_many, input,
- <<-QL.strip_heredoc
- clientMutationId
- errors
- todos {
- id
- state
- }
- QL
+ graphql_mutation(
+ :todo_restore_many,
+ input,
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ todos {
+ id
+ state
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/mutations/todos/restore_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
index 868298763ec..1ebd04432be 100644
--- a/spec/requests/api/graphql/mutations/todos/restore_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
@@ -19,15 +19,17 @@ RSpec.describe 'Restoring Todos', feature_category: :team_planning do
let(:input) { { id: todo1.to_global_id.to_s } }
let(:mutation) do
- graphql_mutation(:todo_restore, input,
- <<-QL.strip_heredoc
- clientMutationId
- errors
- todo {
- id
- state
- }
- QL
+ graphql_mutation(
+ :todo_restore,
+ input,
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ todo {
+ id
+ state
+ }
+ QL
)
end
diff --git a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
index 7c48f324d24..c8819f1e38f 100644
--- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
+++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
@@ -6,8 +6,15 @@ RSpec.describe 'rendering namespace statistics', feature_category: :metrics do
include GraphqlHelpers
let(:namespace) { user.namespace }
- let!(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace, packages_size: 5.gigabytes, uploads_size: 3.gigabytes) }
let(:user) { create(:user) }
+ let!(:statistics) do
+ create(
+ :namespace_root_storage_statistics,
+ namespace: namespace,
+ packages_size: 5.gigabytes,
+ uploads_size: 3.gigabytes
+ )
+ end
let(:query) do
graphql_query_for(
diff --git a/spec/requests/api/graphql/organizations/organization_query_spec.rb b/spec/requests/api/graphql/organizations/organization_query_spec.rb
index d02158382eb..c243e0613ad 100644
--- a/spec/requests/api/graphql/organizations/organization_query_spec.rb
+++ b/spec/requests/api/graphql/organizations/organization_query_spec.rb
@@ -79,7 +79,10 @@ RSpec.describe 'getting organization information', feature_category: :cell do
<<~FIELDS
organizationUsers {
nodes {
- badges
+ badges {
+ text
+ variant
+ }
id
user {
id
@@ -94,7 +97,7 @@ RSpec.describe 'getting organization information', feature_category: :cell do
organization_user_node = graphql_data_at(:organization, :organizationUsers, :nodes).first
expected_attributes = {
- "badges" => ["It's you!"],
+ "badges" => [{ "text" => "It's you!", "variant" => 'muted' }],
"id" => organization_user.to_global_id.to_s,
"user" => { "id" => user.to_global_id.to_s }
}
diff --git a/spec/requests/api/graphql/project/base_service_spec.rb b/spec/requests/api/graphql/project/base_service_spec.rb
index b27cddea07b..646ff8dd8a8 100644
--- a/spec/requests/api/graphql/project/base_service_spec.rb
+++ b/spec/requests/api/graphql/project/base_service_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe 'query Jira service', feature_category: :system_access do
it 'retuns list of jira imports' do
service_types = services.map { |s| s['type'] }
- expect(service_types).to match_array(%w(BugzillaService JiraService RedmineService))
+ expect(service_types).to match_array(%w[BugzillaService JiraService RedmineService])
end
end
end
diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb
index 9a40a972256..c86d3bdd14c 100644
--- a/spec/requests/api/graphql/project/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/project/container_repositories_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe 'getting container repositories in a project', feature_category:
before do
stub_container_registry_config(enabled: true)
container_repositories.each do |repository|
- stub_container_registry_tags(repository: repository.path, tags: %w(tag1 tag2 tag3), with_manifest: false)
+ stub_container_registry_tags(repository: repository.path, tags: %w[tag1 tag2 tag3], with_manifest: false)
end
end
@@ -141,7 +141,7 @@ RSpec.describe 'getting container repositories in a project', feature_category:
end
before do
- stub_container_registry_tags(repository: container_repository.path, tags: %w(tag4 tag5 tag6), with_manifest: false)
+ stub_container_registry_tags(repository: container_repository.path, tags: %w[tag4 tag5 tag6], with_manifest: false)
end
it 'returns the searched container repository' do
@@ -175,11 +175,11 @@ RSpec.describe 'getting container repositories in a project', feature_category:
let_it_be(:container_repository5) { create(:container_repository, name: 'e', project: sort_project) }
before do
- stub_container_registry_tags(repository: container_repository1.path, tags: %w(tag1 tag1 tag3), with_manifest: false)
- stub_container_registry_tags(repository: container_repository2.path, tags: %w(tag4 tag5 tag6), with_manifest: false)
- stub_container_registry_tags(repository: container_repository3.path, tags: %w(tag7 tag8), with_manifest: false)
- stub_container_registry_tags(repository: container_repository4.path, tags: %w(tag9), with_manifest: false)
- stub_container_registry_tags(repository: container_repository5.path, tags: %w(tag10 tag11), with_manifest: false)
+ stub_container_registry_tags(repository: container_repository1.path, tags: %w[tag1 tag1 tag3], with_manifest: false)
+ stub_container_registry_tags(repository: container_repository2.path, tags: %w[tag4 tag5 tag6], with_manifest: false)
+ stub_container_registry_tags(repository: container_repository3.path, tags: %w[tag7 tag8], with_manifest: false)
+ stub_container_registry_tags(repository: container_repository4.path, tags: %w[tag9], with_manifest: false)
+ stub_container_registry_tags(repository: container_repository5.path, tags: %w[tag10 tag11], with_manifest: false)
end
def pagination_query(params)
diff --git a/spec/requests/api/graphql/project/data_transfer_spec.rb b/spec/requests/api/graphql/project/data_transfer_spec.rb
index aafa8d65eb9..79b2b10419b 100644
--- a/spec/requests/api/graphql/project/data_transfer_spec.rb
+++ b/spec/requests/api/graphql/project/data_transfer_spec.rb
@@ -68,45 +68,21 @@ RSpec.describe 'project data transfers', feature_category: :source_code_manageme
context 'when user has enough permissions' do
before do
project.add_owner(current_user)
+ subject
end
- context 'when data_transfer_monitoring_mock_data is NOT enabled' do
- before do
- stub_feature_flags(data_transfer_monitoring_mock_data: false)
- subject
- end
-
- it 'returns real results' do
- expect(response).to have_gitlab_http_status(:ok)
+ it 'returns real results' do
+ expect(response).to have_gitlab_http_status(:ok)
- expect(egress_data.count).to eq(2)
+ expect(egress_data.count).to eq(2)
- expect(egress_data.first.keys).to match_array(
- %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress]
- )
+ expect(egress_data.first.keys).to match_array(
+ %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress]
+ )
- expect(egress_data.pluck('repositoryEgress')).to match_array(%w[1 2])
- end
-
- it_behaves_like 'a working graphql query'
+ expect(egress_data.pluck('repositoryEgress')).to match_array(%w[1 2])
end
- context 'when data_transfer_monitoring_mock_data is enabled' do
- before do
- stub_feature_flags(data_transfer_monitoring_mock_data: true)
- subject
- end
-
- it 'returns mock results' do
- expect(response).to have_gitlab_http_status(:ok)
-
- expect(egress_data.count).to eq(12)
- expect(egress_data.first.keys).to match_array(
- %w[date totalEgress repositoryEgress artifactsEgress packagesEgress registryEgress]
- )
- end
-
- it_behaves_like 'a working graphql query'
- end
+ it_behaves_like 'a working graphql query'
end
end
diff --git a/spec/requests/api/graphql/project/environments_spec.rb b/spec/requests/api/graphql/project/environments_spec.rb
index 3a863bd3d77..94ce6b797cd 100644
--- a/spec/requests/api/graphql/project/environments_spec.rb
+++ b/spec/requests/api/graphql/project/environments_spec.rb
@@ -150,7 +150,7 @@ RSpec.describe 'Project Environments query', feature_category: :continuous_deliv
end
describe 'last deployments of environments' do
- ::Deployment.statuses.each do |status, _| # rubocop:disable RSpec/UselessDynamicDefinition
+ ::Deployment.statuses.each_key do |status| # rubocop:disable RSpec/UselessDynamicDefinition -- `status` used in `let_it_be`
let_it_be(:"production_#{status}_deployment") do
create(:deployment, status.to_sym, environment: production, project: project)
end
diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
index 6cbc70022ed..c2910938acf 100644
--- a/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
+++ b/spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
@@ -119,7 +119,7 @@ RSpec.describe 'sentry errors requests', feature_category: :error_tracking do
end
let(:error_data) { graphql_data.dig('project', 'sentryErrors', 'errors', 'nodes') }
- let(:pagination_data) { graphql_data.dig('project', 'sentryErrors', 'errors', 'pageInfo') }
+ let(:pagination_data) { graphql_data.dig('project', 'sentryErrors', 'errors', 'pageInfo') }
it_behaves_like 'a working graphql query' do
before do
diff --git a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
index a15e4c1e792..bc56df5b6ff 100644
--- a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
+++ b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
@@ -73,7 +73,7 @@ RSpec.describe 'Getting versions related to an issue', feature_category: :design
post_graphql(query, current_user: current_user)
keys = graphql_data.dig(*edges_path).first['node'].keys
- expect(keys).to match_array(%w(id sha createdAt author))
+ expect(keys).to match_array(%w[id sha createdAt author])
end
end
diff --git a/spec/requests/api/graphql/project/issue_spec.rb b/spec/requests/api/graphql/project/issue_spec.rb
index bc90f9e89e6..8d21a9f0394 100644
--- a/spec/requests/api/graphql/project/issue_spec.rb
+++ b/spec/requests/api/graphql/project/issue_spec.rb
@@ -108,7 +108,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid)', feature_category: :team_pla
let(:object_field_name) { :design }
let(:no_argument_error) do
- custom_graphql_error(path, a_string_matching(%r/id or filename/))
+ custom_graphql_error(path, a_string_matching(%r{id or filename}))
end
let_it_be(:object_on_other_issue) { create(:design, issue: issue_b) }
@@ -134,7 +134,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid)', feature_category: :team_pla
it 'raises an error' do
post_query
- expect(graphql_errors).to include(custom_graphql_error(path, a_string_matching(%r/id or sha/)))
+ expect(graphql_errors).to include(custom_graphql_error(path, a_string_matching(%r{id or sha})))
end
end
diff --git a/spec/requests/api/graphql/project/jira_import_spec.rb b/spec/requests/api/graphql/project/jira_import_spec.rb
index 25cea0238ef..a1d340b3500 100644
--- a/spec/requests/api/graphql/project/jira_import_spec.rb
+++ b/spec/requests/api/graphql/project/jira_import_spec.rb
@@ -96,7 +96,7 @@ RSpec.describe 'query Jira import data', feature_category: :importers do
total_issue_count = jira_imports.map { |ji| ji.dig('totalIssueCount') }
expect(jira_imports.size).to eq 2
- expect(jira_proket_keys).to eq %w(BB AA)
+ expect(jira_proket_keys).to eq %w[BB AA]
expect(usernames).to eq [current_user.username, current_user.username]
expect(imported_issues_count).to eq [2, 2]
expect(failed_issues_count).to eq [1, 2]
diff --git a/spec/requests/api/graphql/project/jira_projects_spec.rb b/spec/requests/api/graphql/project/jira_projects_spec.rb
index 3cd689deda5..2859e8a7c99 100644
--- a/spec/requests/api/graphql/project/jira_projects_spec.rb
+++ b/spec/requests/api/graphql/project/jira_projects_spec.rb
@@ -55,8 +55,8 @@ RSpec.describe 'query Jira projects', feature_category: :integrations do
project_ids = jira_projects.map { |jp| jp['projectId'] }
expect(jira_projects.size).to eq(2)
- expect(project_keys).to eq(%w(EX ABC))
- expect(project_names).to eq(%w(Example Alphabetical))
+ expect(project_keys).to eq(%w[EX ABC])
+ expect(project_names).to eq(%w[Example Alphabetical])
expect(project_ids).to eq([10000, 10001])
end
@@ -69,8 +69,8 @@ RSpec.describe 'query Jira projects', feature_category: :integrations do
project_ids = jira_projects.map { |jp| jp['projectId'] }
expect(jira_projects.size).to eq(1)
- expect(project_keys).to eq(%w(EX))
- expect(project_names).to eq(%w(Example))
+ expect(project_keys).to eq(%w[EX])
+ expect(project_names).to eq(%w[Example])
expect(project_ids).to eq([10000])
end
end
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index c274199e65b..23be9fa5286 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe 'getting merge request information nested in a project', feature_
it_behaves_like 'a working graphql query' do
# we exclude Project.pipeline because it needs arguments,
- # codequalityReportsComparer because no pipeline exist yet
+ # codequalityReportsComparer because it is behind a feature flag
# and runners because the user is not an admin and therefore has no access
let(:excluded) { %w[jobs pipeline runners codequalityReportsComparer] }
let(:mr_fields) { all_graphql_fields_for('MergeRequest', excluded: excluded) }
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 543de43bcf3..176a02df0be 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -48,8 +48,11 @@ RSpec.describe 'getting merge request listings nested in a project', feature_cat
end
it_behaves_like 'a working graphql query' do
+ # we exclude codequalityReportsComparer because it is behind feature flag
+ let(:excluded) { %w[codequalityReportsComparer] }
+
let(:query) do
- query_merge_requests(all_graphql_fields_for('MergeRequest', max_depth: 2))
+ query_merge_requests(all_graphql_fields_for('MergeRequest', max_depth: 2, excluded: excluded))
end
before do
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index 8d4a39d6b30..8206d076d2e 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re
let(:path) { path_prefix }
let(:release_fields) do
- %{
+ %(
tagName
tagPath
description
@@ -45,7 +45,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re
createdAt
releasedAt
upcomingRelease
- }
+ )
end
before do
@@ -176,14 +176,14 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re
let(:path) { path_prefix + %w[links] }
let(:release_fields) do
- query_graphql_field(:links, nil, %{
+ query_graphql_field(:links, nil, %(
selfUrl
openedMergeRequestsUrl
mergedMergeRequestsUrl
closedMergeRequestsUrl
openedIssuesUrl
closedIssuesUrl
- })
+ ))
end
it 'finds all release links' do
@@ -225,7 +225,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re
let(:path) { path_prefix }
let(:release_fields) do
- %{
+ %(
tagName
tagPath
description
@@ -234,7 +234,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re
createdAt
releasedAt
upcomingRelease
- }
+ )
end
before do
@@ -358,14 +358,14 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re
let(:path) { path_prefix + %w[links] }
let(:release_fields) do
- query_graphql_field(:links, nil, %{
+ query_graphql_field(:links, nil, %(
selfUrl
openedMergeRequestsUrl
mergedMergeRequestsUrl
closedMergeRequestsUrl
openedIssuesUrl
closedIssuesUrl
- })
+ ))
end
it 'finds only selfUrl' do
@@ -547,10 +547,10 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re
let(:current_user) { developer }
let(:release_fields) do
- %{
+ %(
releasedAt
upcomingRelease
- }
+ )
end
before do
@@ -588,13 +588,13 @@ RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :re
let_it_be_with_reload(:release) { create(:release, project: project) }
let(:release_fields) do
- %{
+ %(
milestones {
nodes {
title
}
}
- }
+ )
end
let(:actual_milestone_title_order) do
diff --git a/spec/requests/api/graphql/project/terraform/state_spec.rb b/spec/requests/api/graphql/project/terraform/state_spec.rb
index 1889e7a1064..28f3868c7cf 100644
--- a/spec/requests/api/graphql/project/terraform/state_spec.rb
+++ b/spec/requests/api/graphql/project/terraform/state_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe 'query a single terraform state', feature_category: :infrastructu
query_graphql_field(
:terraformState,
{ name: terraform_state.name },
- %{
+ %(
id
name
lockedAt
@@ -45,7 +45,7 @@ RSpec.describe 'query a single terraform state', feature_category: :infrastructu
lockedByUser {
id
}
- }
+ )
)
)
end
diff --git a/spec/requests/api/graphql/project/terraform/states_spec.rb b/spec/requests/api/graphql/project/terraform/states_spec.rb
index 7a789a5d481..d6cf3a52649 100644
--- a/spec/requests/api/graphql/project/terraform/states_spec.rb
+++ b/spec/requests/api/graphql/project/terraform/states_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'query terraform states', feature_category: :infrastructure_as_co
graphql_query_for(
:project,
{ fullPath: project.full_path },
- %{
+ %(
terraformStates {
count
nodes {
@@ -45,7 +45,7 @@ RSpec.describe 'query terraform states', feature_category: :infrastructure_as_co
}
}
}
- }
+ )
)
end
diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb
index d5d3d6c578f..d0f80bcfebe 100644
--- a/spec/requests/api/graphql/project/work_items_spec.rb
+++ b/spec/requests/api/graphql/project/work_items_spec.rb
@@ -350,7 +350,9 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
end
context 'when fetching work item linked items widget' do
- let_it_be(:related_items) { create_list(:work_item, 3, project: project, milestone: milestone1) }
+ let_it_be(:other_project) { create(:project, :repository, :public, group: group) }
+ let_it_be(:other_milestone) { create(:milestone, project: other_project) }
+ let_it_be(:related_items) { create_list(:work_item, 3, project: other_project, milestone: other_milestone) }
let(:fields) do
<<~GRAPHQL
@@ -384,21 +386,24 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
before do
create(:work_item_link, source: item1, target: related_items[0], link_type: 'relates_to')
+ create(:work_item_link, source: item2, target: related_items[0], link_type: 'relates_to')
end
it 'executes limited number of N+1 queries', :use_sql_query_cache do
+ post_graphql(query, current_user: current_user) # warm-up
+
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: current_user)
end
- create(:work_item_link, source: item1, target: related_items[1], link_type: 'relates_to')
- create(:work_item_link, source: item1, target: related_items[2], link_type: 'relates_to')
+ [item1, item2].each do |item|
+ create(:work_item_link, source: item, target: related_items[1], link_type: 'relates_to')
+ create(:work_item_link, source: item, target: related_items[2], link_type: 'relates_to')
+ end
expect_graphql_errors_to_be_empty
- # TODO: Fix N+1 queries executed for the linked work item widgets
- # https://gitlab.com/gitlab-org/gitlab/-/issues/420605
expect { post_graphql(query, current_user: current_user) }
- .not_to exceed_all_query_limit(control).with_threshold(11)
+ .not_to exceed_all_query_limit(control)
end
end
diff --git a/spec/requests/api/graphql/projects/projects_spec.rb b/spec/requests/api/graphql/projects/projects_spec.rb
new file mode 100644
index 00000000000..84b8c2285f0
--- /dev/null
+++ b/spec/requests/api/graphql/projects/projects_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting a collection of projects', feature_category: :source_code_management do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group, name: 'public-group') }
+ let_it_be(:projects) { create_list(:project, 5, :public, group: group) }
+ let_it_be(:other_project) { create(:project, :public, group: group) }
+
+ let(:filters) { {} }
+
+ let(:query) do
+ graphql_query_for(
+ :projects,
+ filters,
+ "nodes {#{all_graphql_fields_for('Project', max_depth: 1, excluded: ['productAnalyticsState'])} }"
+ )
+ end
+
+ before_all do
+ group.add_developer(current_user)
+ end
+
+ context 'when providing full_paths filter' do
+ let(:project_full_paths) { projects.map(&:full_path) }
+ let(:filters) { { full_paths: project_full_paths } }
+
+ let(:single_project_query) do
+ graphql_query_for(
+ :projects,
+ { full_paths: [project_full_paths.first] },
+ "nodes {#{all_graphql_fields_for('Project', max_depth: 1, excluded: ['productAnalyticsState'])} }"
+ )
+ end
+
+ it_behaves_like 'a working graphql query that returns data' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
+ it 'avoids N+1 queries', :use_sql_query_cache, :clean_gitlab_redis_cache do
+ post_graphql(single_project_query, current_user: current_user)
+
+ query_count = ActiveRecord::QueryRecorder.new do
+ post_graphql(single_project_query, current_user: current_user)
+ end.count
+
+ # There is an N+1 query for max_member_access_for_user_ids
+ expect do
+ post_graphql(query, current_user: current_user)
+ end.not_to exceed_all_query_limit(query_count + 5)
+ end
+
+ it 'returns the expected projects' do
+ post_graphql(query, current_user: current_user)
+ returned_full_paths = graphql_data_at(:projects, :nodes).pluck('fullPath')
+
+ expect(returned_full_paths).to match_array(project_full_paths)
+ end
+
+ context 'when users provides more than 50 full_paths' do
+ let(:filters) { { full_paths: Array.new(51) { other_project.full_path } } }
+
+ it 'returns an error' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_errors).to contain_exactly(
+ hash_including('message' => _('You cannot provide more than 50 full_paths'))
+ )
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/user_spec.rb b/spec/requests/api/graphql/user_spec.rb
index 41ee233dfc5..22ebc1be964 100644
--- a/spec/requests/api/graphql/user_spec.rb
+++ b/spec/requests/api/graphql/user_spec.rb
@@ -113,4 +113,36 @@ RSpec.describe 'User', feature_category: :user_profile do
end
end
end
+
+ describe 'organizations field' do
+ let_it_be(:organization_user) { create(:organization_user, user: current_user) }
+ let_it_be(:organization) { organization_user.organization }
+ let_it_be(:another_organization) { create(:organization) }
+ let_it_be(:another_user) { create(:user) }
+
+ let(:query) do
+ graphql_query_for(
+ :user,
+ { username: current_user.username.to_s.upcase },
+ 'organizations { nodes { path } }'
+ )
+ end
+
+ context 'with permission' do
+ it 'returns the relevant organization details' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data.dig('user', 'organizations', 'nodes').pluck('path'))
+ .to match_array(organization.path)
+ end
+ end
+
+ context 'without permission' do
+ it 'does not return organization details' do
+ post_graphql(query, current_user: another_user)
+
+ expect(graphql_data.dig('user', 'organizations', 'nodes')).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index b8575b25e0a..36a27abd982 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -619,6 +619,47 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
)
end
+ context 'when inaccessible links are present' do
+ let_it_be(:no_access_item) { create(:work_item, title: "PRIVATE", project: create(:project, :private)) }
+
+ before do
+ create(:work_item_link, source: work_item, target: no_access_item, link_type: 'relates_to')
+ end
+
+ it 'returns only items that the user has access to' do
+ expect(graphql_dig_at(work_item_data, :widgets, "linkedItems", "nodes", "linkId"))
+ .to match_array([link1.to_gid.to_s, link2.to_gid.to_s])
+ end
+ end
+
+ context 'when limiting the number of results' do
+ it_behaves_like 'sorted paginated query' do
+ include_context 'no sort argument'
+
+ let(:first_param) { 1 }
+ let(:all_records) { [link1, link2] }
+ let(:data_path) { ['workItem', 'widgets', "linkedItems", -1] }
+
+ def widget_fields(args)
+ query_graphql_field(
+ :widgets, {}, query_graphql_field(
+ '... on WorkItemWidgetLinkedItems', {}, query_graphql_field(
+ 'linkedItems', args, "#{page_info} nodes { linkId }"
+ )
+ )
+ )
+ end
+
+ def pagination_query(params)
+ graphql_query_for('workItem', { 'id' => global_id }, widget_fields(params))
+ end
+
+ def pagination_results_data(nodes)
+ nodes.map { |item| GlobalID::Locator.locate(item['linkId']) }
+ end
+ end
+ end
+
context 'when filtering by link type' do
let(:work_item_fields) do
<<~GRAPHQL
@@ -664,20 +705,6 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
end
describe 'notes widget' do
- let(:work_item_fields) do
- <<~GRAPHQL
- id
- widgets {
- type
- ... on WorkItemWidgetNotes {
- system: discussions(filter: ONLY_ACTIVITY, first: 10) { nodes { id notes { nodes { id system internal body } } } },
- comments: discussions(filter: ONLY_COMMENTS, first: 10) { nodes { id notes { nodes { id system internal body } } } },
- all_notes: discussions(filter: ALL_NOTES, first: 10) { nodes { id notes { nodes { id system internal body } } } }
- }
- }
- GRAPHQL
- end
-
context 'when fetching award emoji from notes' do
let(:work_item_fields) do
<<~GRAPHQL
@@ -768,6 +795,26 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
expect { post_graphql(query, current_user: developer) }.not_to exceed_query_limit(control).with_threshold(4)
expect_graphql_errors_to_be_empty
end
+
+ context 'when work item is associated with a group' do
+ let_it_be(:group_work_item) { create(:work_item, :group_level, namespace: group) }
+ let_it_be(:group_work_item_note) { create(:note, noteable: group_work_item, author: developer, project: nil) }
+ let(:global_id) { group_work_item.to_gid.to_s }
+
+ before_all do
+ create(:award_emoji, awardable: group_work_item_note, name: 'rocket', user: developer)
+ end
+
+ it 'returns notes for the group work item' do
+ all_widgets = graphql_dig_at(work_item_data, :widgets)
+ notes_widget = all_widgets.find { |x| x['type'] == 'NOTES' }
+ notes = graphql_dig_at(notes_widget['discussions'], :nodes).flat_map { |d| d['notes']['nodes'] }
+
+ expect(notes).to contain_exactly(
+ hash_including('body' => group_work_item_note.note)
+ )
+ end
+ end
end
end
diff --git a/spec/requests/api/group_packages_spec.rb b/spec/requests/api/group_packages_spec.rb
index 0b4f6130132..0786815c787 100644
--- a/spec/requests/api/group_packages_spec.rb
+++ b/spec/requests/api/group_packages_spec.rb
@@ -137,6 +137,29 @@ RSpec.describe API::GroupPackages, feature_category: :package_registry do
it_behaves_like 'filters on each package_type', is_project: false
+ context 'filtering on package_version' do
+ include_context 'package filter context'
+
+ let!(:package1) { create(:nuget_package, project: project, version: '2.0.4') }
+ let!(:package2) { create(:nuget_package, project: project) }
+
+ it 'returns the versioned package' do
+ url = group_filter_url(:version, '2.0.4')
+ get api(url, user)
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['version']).to eq(package1.version)
+ end
+
+ it 'include_versionless has no effect' do
+ url = "/groups/#{group.id}/packages?package_version=2.0.4&include_versionless=true"
+ get api(url, user)
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['version']).to eq(package1.version)
+ end
+ end
+
context 'does not accept non supported package_type value' do
include_context 'package filter context'
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 662e11f7cfb..327dfd0a76b 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -572,7 +572,8 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
expect(json_response['require_two_factor_authentication']).to eq(group1.require_two_factor_authentication)
expect(json_response['two_factor_grace_period']).to eq(group1.two_factor_grace_period)
expect(json_response['auto_devops_enabled']).to eq(group1.auto_devops_enabled)
- expect(json_response['emails_disabled']).to eq(group1.emails_disabled)
+ expect(json_response['emails_disabled']).to eq(group1.emails_disabled?)
+ expect(json_response['emails_enabled']).to eq(group1.emails_enabled?)
expect(json_response['mentions_disabled']).to eq(group1.mentions_disabled)
expect(json_response['project_creation_level']).to eq('maintainer')
expect(json_response['subgroup_creation_level']).to eq('maintainer')
@@ -870,6 +871,38 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
end
end
+ before do
+ stub_application_setting(update_namespace_name_rate_limit: 1)
+ end
+
+ it 'increments the update_namespace_name rate limit' do
+ put api("/groups/#{group1.id}", user1), params: { name: "#{new_group_name}_1" }
+
+ expect(::Gitlab::ApplicationRateLimiter.peek(:update_namespace_name, scope: group1)).to be_falsey
+
+ put api("/groups/#{group1.id}", user1), params: { name: "#{new_group_name}_2" }
+
+ expect(::Gitlab::ApplicationRateLimiter.peek(:update_namespace_name, scope: group1)).to be_truthy
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(group1.reload.name).to eq("#{new_group_name}_2")
+ end
+
+ context 'a name is not passed in' do
+ it 'does not mark name update throttling' do
+ expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
+
+ put api("/groups/#{group1.id}", user1), params: { path: 'another/path' }
+ end
+ end
+
+ context 'an empty name is passed in' do
+ it 'does not mark name update throttling' do
+ expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
+
+ put api("/groups/#{group1.id}", user1), params: { name: '' }
+ end
+ end
+
context 'when authenticated as the group owner' do
it 'updates the group', :aggregate_failures do
workhorse_form_with_file(
@@ -895,7 +928,8 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
expect(json_response['require_two_factor_authentication']).to eq(false)
expect(json_response['two_factor_grace_period']).to eq(48)
expect(json_response['auto_devops_enabled']).to eq(nil)
- expect(json_response['emails_disabled']).to eq(nil)
+ expect(json_response['emails_disabled']).to eq(false)
+ expect(json_response['emails_enabled']).to eq(true)
expect(json_response['mentions_disabled']).to eq(nil)
expect(json_response['project_creation_level']).to eq("noone")
expect(json_response['subgroup_creation_level']).to eq("maintainer")
diff --git a/spec/requests/api/helm_packages_spec.rb b/spec/requests/api/helm_packages_spec.rb
index d6afd6f86ff..75f60c59759 100644
--- a/spec/requests/api/helm_packages_spec.rb
+++ b/spec/requests/api/helm_packages_spec.rb
@@ -101,6 +101,12 @@ RSpec.describe API::HelmPackages, feature_category: :package_registry do
end
it_behaves_like 'deploy token for package GET requests'
+
+ context 'when format param is not nil' do
+ let(:url) { "/projects/#{project.id}/packages/helm/stable/charts/#{package.name}-#{package.version}.tgz.prov" }
+
+ it_behaves_like 'rejects helm packages access', :maintainer, :not_found, '{"message":"404 Format prov Not Found"}'
+ end
end
describe 'POST /api/v4/projects/:id/packages/helm/api/:channel/charts/authorize' do
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index cf0cd9a2e85..e59633b6d35 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -111,12 +111,12 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
it 'returns new recovery codes when the user exists' do
allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(true)
allow_any_instance_of(User)
- .to receive(:generate_otp_backup_codes!).and_return(%w(119135e5a3ebce8e 34bd7b74adbc8861))
+ .to receive(:generate_otp_backup_codes!).and_return(%w[119135e5a3ebce8e 34bd7b74adbc8861])
subject
expect(json_response['success']).to be_truthy
- expect(json_response['recovery_codes']).to match_array(%w(119135e5a3ebce8e 34bd7b74adbc8861))
+ expect(json_response['recovery_codes']).to match_array(%w[119135e5a3ebce8e 34bd7b74adbc8861])
end
end
@@ -200,7 +200,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
params: {
key_id: key.id,
name: 'newtoken',
- scopes: %w(read_api badscope read_repository)
+ scopes: %w[read_api badscope read_repository]
},
headers: gitlab_shell_internal_api_request_header
@@ -216,14 +216,14 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
params: {
key_id: key.id,
name: 'newtoken',
- scopes: %w(read_api read_repository),
+ scopes: %w[read_api read_repository],
expires_at: max_pat_access_token_lifetime
},
headers: gitlab_shell_internal_api_request_header
expect(json_response['success']).to be_truthy
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
- expect(json_response['scopes']).to match_array(%w(read_api read_repository))
+ expect(json_response['scopes']).to match_array(%w[read_api read_repository])
expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
end
end
@@ -236,14 +236,14 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
params: {
key_id: key.id,
name: 'newtoken',
- scopes: %w(read_api read_repository),
+ scopes: %w[read_api read_repository],
expires_at: 365.days.from_now
},
headers: gitlab_shell_internal_api_request_header
expect(json_response['success']).to be_truthy
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
- expect(json_response['scopes']).to match_array(%w(read_api read_repository))
+ expect(json_response['scopes']).to match_array(%w[read_api read_repository])
expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
end
end
@@ -560,6 +560,20 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
end
end
+ context 'when Gitaly provides a relative_path argument', :request_store do
+ subject { push(key, project, relative_path: relative_path) }
+
+ let(:relative_path) { 'relative_path' }
+
+ it 'stores relative_path value in RequestStore' do
+ allow(Gitlab::SafeRequestStore).to receive(:[]=).and_call_original
+ expect(Gitlab::SafeRequestStore).to receive(:[]=).with(:gitlab_git_relative_path, relative_path)
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
context "git push with project.wiki" do
subject { push(key, project.wiki, env: env.to_json) }
@@ -744,6 +758,17 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-mep-mep' => 'false')
end
end
+
+ context 'with audit event' do
+ it 'does not send a git streaming audit event' do
+ expect(::Gitlab::Audit::Auditor).not_to receive(:audit)
+
+ pull(key, project)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response["need_audit"]).to be_falsy
+ end
+ end
end
context "git push" do
@@ -757,6 +782,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(json_response["gl_project_path"]).to eq(project.full_path)
expect(json_response["gl_key_type"]).to eq("key")
expect(json_response["gl_key_id"]).to eq(key.id)
+ expect(json_response["need_audit"]).to be_falsy
expect(json_response["gitaly"]).not_to be_nil
expect(json_response["gitaly"]["repository"]).not_to be_nil
expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
@@ -885,7 +911,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
{
'action' => 'geo_proxy_to_primary',
'data' => {
- 'api_endpoints' => %w{geo/proxy_git_ssh/info_refs_receive_pack geo/proxy_git_ssh/receive_pack},
+ 'api_endpoints' => %w[geo/proxy_git_ssh/info_refs_receive_pack geo/proxy_git_ssh/receive_pack],
'gl_username' => 'testuser',
'primary_repo' => 'http://localhost:3000/testuser/repo.git'
}
@@ -1515,7 +1541,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
describe 'POST /internal/pre_receive' do
let(:valid_params) do
- { gl_repository: gl_repository }
+ { gl_repository: gl_repository, user_id: user.id }
end
it 'decreases the reference counter and returns the result' do
@@ -1527,6 +1553,12 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(json_response['reference_counter_increased']).to be(true)
end
+
+ it 'sticks to the primary' do
+ expect(User.sticking).to receive(:find_caught_up_replica).with(:user, user.id)
+
+ post api("/internal/pre_receive"), params: valid_params, headers: gitlab_shell_internal_api_request_header
+ end
end
describe 'POST /internal/two_factor_config' do
diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb
index 1eeb3404157..7e2a778f433 100644
--- a/spec/requests/api/internal/pages_spec.rb
+++ b/spec/requests/api/internal/pages_spec.rb
@@ -46,17 +46,7 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
end
end
- context 'when authenticated' do
- before do
- project.update_pages_deployment!(create(:pages_deployment, project: project))
- end
-
- around do |example|
- freeze_time do
- example.run
- end
- end
-
+ context 'when authenticated', :freeze_time do
context 'when domain does not exist' do
it 'responds with 204 no content' do
get api('/internal/pages'), headers: auth_header, params: { host: 'any-domain.gitlab.io' }
@@ -79,10 +69,6 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
end
context 'when there are no pages deployed for the related project' do
- before do
- project.mark_pages_as_not_deployed
- end
-
it 'responds with 204 No Content' do
get api('/internal/pages'), headers: auth_header, params: { host: 'pages.io' }
@@ -91,9 +77,7 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
end
context 'when there are pages deployed for the related project' do
- before do
- project.mark_pages_as_deployed
- end
+ let!(:deployment) { create(:pages_deployment, project: project) }
it 'domain lookup is case insensitive' do
get api('/internal/pages'), headers: auth_header, params: { host: 'Pages.IO' }
@@ -110,7 +94,6 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
expect(json_response['certificate']).to eq(pages_domain.certificate)
expect(json_response['key']).to eq(pages_domain.key)
- deployment = project.pages_metadatum.pages_deployment
expect(json_response['lookup_paths']).to eq(
[
{
@@ -144,10 +127,6 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
end
context 'when there are no pages deployed for the related project' do
- before do
- project.mark_pages_as_not_deployed
- end
-
it 'responds with 204 No Content' do
get api('/internal/pages'), headers: auth_header, params: { host: 'unique-domain.example.com' }
@@ -156,9 +135,7 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
end
context 'when there are pages deployed for the related project' do
- before do
- project.mark_pages_as_deployed
- end
+ let!(:deployment) { create(:pages_deployment, project: project) }
context 'when the unique domain is disabled' do
before do
@@ -186,7 +163,6 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('internal/pages/virtual_domain')
- deployment = project.pages_metadatum.pages_deployment
expect(json_response['lookup_paths']).to eq(
[
{
@@ -218,10 +194,6 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
end
context 'when there are no pages deployed for the related project' do
- before do
- project.mark_pages_as_not_deployed
- end
-
it 'responds with 204 No Content' do
get api('/internal/pages'), headers: auth_header, params: { host: "#{group.path}.gitlab-pages.io" }
@@ -232,9 +204,7 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
end
context 'when there are pages deployed for the related project' do
- before do
- project.mark_pages_as_deployed
- end
+ let!(:deployment) { create(:pages_deployment, project: project) }
context 'with a regular project' do
it 'responds with the correct domain configuration' do
@@ -243,7 +213,6 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('internal/pages/virtual_domain')
- deployment = project.pages_metadatum.pages_deployment
expect(json_response['lookup_paths']).to eq(
[
{
@@ -274,7 +243,7 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
3.times do
project = create(:project, group: group)
- project.mark_pages_as_deployed
+ create(:pages_deployment, project: project)
end
expect { get api('/internal/pages'), headers: auth_header, params: { host: "#{group.path}.gitlab-pages.io" } }
@@ -292,7 +261,6 @@ RSpec.describe API::Internal::Pages, feature_category: :pages do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('internal/pages/virtual_domain')
- deployment = project.pages_metadatum.pages_deployment
expect(json_response['lookup_paths']).to eq(
[
{
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
index eaa3c46d0ca..0ae65479d5e 100644
--- a/spec/requests/api/issues/get_group_issues_spec.rb
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -350,7 +350,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
get api(base_url, admin)
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.last.keys).to include(*%w(id iid project_id title description))
+ expect(json_response.last.keys).to include(*%w[id iid project_id title description])
expect(json_response.last).not_to have_key('subscribed')
end
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index 137fba66eaa..9e54ec08486 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -530,7 +530,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
get api("#{base_url}/issues", user)
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.last.keys).to include(*%w(id iid project_id title description))
+ expect(json_response.last.keys).to include(*%w[id iid project_id title description])
expect(json_response.last).not_to have_key('subscribed')
end
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index af289352778..ed71089c5a9 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -638,7 +638,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end
it 'returns an empty array if no issue matches labels with labels param as array' do
- get api('/issues', user), params: { labels: %w(foo bar) }
+ get api('/issues', user), params: { labels: %w[foo bar] }
expect_paginated_array_response([])
end
@@ -914,7 +914,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end
it 'fails to sort with non predefined options' do
- %w(milestone abracadabra).each do |sort_opt|
+ %w[milestone abracadabra].each do |sort_opt|
get api('/issues', user), params: { order_by: sort_opt, sort: 'asc' }
expect(response).to have_gitlab_http_status(:bad_request)
end
diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb
index 1cd20680afb..60142e7e151 100644
--- a/spec/requests/api/issues/post_projects_issues_spec.rb
+++ b/spec/requests/api/issues/post_projects_issues_spec.rb
@@ -183,7 +183,7 @@ RSpec.describe API::Issues, :aggregate_failures, feature_category: :team_plannin
expect(response).to have_gitlab_http_status(:created)
expect(json_response['title']).to eq('new issue')
expect(json_response['description']).to be_nil
- expect(json_response['labels']).to eq(%w(label label2))
+ expect(json_response['labels']).to eq(%w[label label2])
expect(json_response['confidential']).to be_falsy
expect(json_response['assignee']['name']).to eq(user2.name)
expect(json_response['assignees'].first['name']).to eq(user2.name)
@@ -191,12 +191,12 @@ RSpec.describe API::Issues, :aggregate_failures, feature_category: :team_plannin
it 'creates a new project issue with labels param as array' do
post api("/projects/#{project.id}/issues", user),
- params: { title: 'new issue', labels: %w(label label2), weight: 3, assignee_ids: [user2.id] }
+ params: { title: 'new issue', labels: %w[label label2], weight: 3, assignee_ids: [user2.id] }
expect(response).to have_gitlab_http_status(:created)
expect(json_response['title']).to eq('new issue')
expect(json_response['description']).to be_nil
- expect(json_response['labels']).to eq(%w(label label2))
+ expect(json_response['labels']).to eq(%w[label label2])
expect(json_response['confidential']).to be_falsy
expect(json_response['assignee']['name']).to eq(user2.name)
expect(json_response['assignees'].first['name']).to eq(user2.name)
@@ -391,7 +391,7 @@ RSpec.describe API::Issues, :aggregate_failures, feature_category: :team_plannin
it 'cannot create new labels with labels param as array' do
expect do
- post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: %w(label label2) }
+ post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: %w[label label2] }
end.not_to change { project.labels.count }
end
end
diff --git a/spec/requests/api/issues/put_projects_issues_spec.rb b/spec/requests/api/issues/put_projects_issues_spec.rb
index dbba31cd4d6..070ef6057dd 100644
--- a/spec/requests/api/issues/put_projects_issues_spec.rb
+++ b/spec/requests/api/issues/put_projects_issues_spec.rb
@@ -359,7 +359,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
it 'updates labels and touches the record with labels param as array', :aggregate_failures do
travel_to(2.minutes.from_now) do
- put api_for_user, params: { labels: %w(foo bar) }
+ put api_for_user, params: { labels: %w[foo bar] }
end
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
index 1f841eefff2..578a4821b5e 100644
--- a/spec/requests/api/maven_packages_spec.rb
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -946,6 +946,22 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
expect(response).to have_gitlab_http_status(:forbidden)
end
+ context 'with basic auth' do
+ where(:token_type) do
+ %i[personal_access_token deploy_token job]
+ end
+
+ with_them do
+ let(:token) { send(token_type).token }
+
+ it "authorizes upload with #{params[:token_type]} token" do
+ authorize_upload({}, headers.merge(basic_auth_header(token_type == :job ? ::Gitlab::Auth::CI_JOB_USER : user.username, token)))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
def authorize_upload(params = {}, request_headers = headers)
put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/maven-metadata.xml/authorize"), params: params, headers: request_headers
end
@@ -1083,6 +1099,22 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
expect(response).to have_gitlab_http_status(:forbidden)
end
+ context 'with basic auth' do
+ where(:token_type) do
+ %i[personal_access_token deploy_token job]
+ end
+
+ with_them do
+ let(:token) { send(token_type).token }
+
+ it "allows upload with #{params[:token_type]} token" do
+ upload_file(params: params, request_headers: headers.merge(basic_auth_header(token_type == :job ? ::Gitlab::Auth::CI_JOB_USER : user.username, token)))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
context 'file name is too long' do
let(:file_name) { 'a' * (Packages::Maven::FindOrCreatePackageService::MAX_FILE_NAME_LENGTH + 1) }
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 8dab9d555cf..feb24a4e73f 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -826,48 +826,20 @@ RSpec.describe API::Members, feature_category: :groups_and_projects do
end
end
- context 'with admin_group_member FF disabled' do
- before do
- stub_feature_flags(admin_group_member: false)
- end
-
- it_behaves_like 'POST /:source_type/:id/members', 'project' do
- let(:source) { project }
- end
-
- it_behaves_like 'POST /:source_type/:id/members', 'group' do
- let(:source) { group }
- end
-
- it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'project' do
- let(:source) { project }
- end
-
- it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'group' do
- let(:source) { group }
- end
+ it_behaves_like 'POST /:source_type/:id/members', 'project' do
+ let(:source) { project }
end
- context 'with admin_group_member FF enabled' do
- before do
- stub_feature_flags(admin_group_member: true)
- end
-
- it_behaves_like 'POST /:source_type/:id/members', 'project' do
- let(:source) { project }
- end
-
- it_behaves_like 'POST /:source_type/:id/members', 'group' do
- let(:source) { group }
- end
+ it_behaves_like 'POST /:source_type/:id/members', 'group' do
+ let(:source) { group }
+ end
- it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'project' do
- let(:source) { project }
- end
+ it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
- it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'group' do
- let(:source) { group }
- end
+ it_behaves_like 'PUT /:source_type/:id/members/:user_id', 'group' do
+ let(:source) { group }
end
it_behaves_like 'DELETE /:source_type/:id/members/:user_id', 'project' do
diff --git a/spec/requests/api/merge_request_approvals_spec.rb b/spec/requests/api/merge_request_approvals_spec.rb
index a1d6abec97e..2de59750273 100644
--- a/spec/requests/api/merge_request_approvals_spec.rb
+++ b/spec/requests/api/merge_request_approvals_spec.rb
@@ -8,8 +8,6 @@ RSpec.describe API::MergeRequestApprovals, feature_category: :source_code_manage
let_it_be(:bot) { create(:user, :project_bot) }
let_it_be(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
let_it_be(:approver) { create :user }
- let_it_be(:group) { create :group }
-
let(:merge_request) { create(:merge_request, :simple, author: user, source_project: project) }
describe 'GET :id/merge_requests/:merge_request_iid/approvals' do
@@ -87,6 +85,28 @@ RSpec.describe API::MergeRequestApprovals, feature_category: :source_code_manage
expect(response).to have_gitlab_http_status(:created)
end
+
+ it 'calls MergeRequests::UpdateReviewerStateService' do
+ unapprover = create(:user)
+
+ project.add_developer(approver)
+ project.add_developer(unapprover)
+ project.add_developer(create(:user))
+
+ create(:approval, user: approver, merge_request: merge_request)
+ create(:approval, user: unapprover, merge_request: merge_request)
+
+ expect_next_instance_of(
+ MergeRequests::UpdateReviewerStateService,
+ project: project, current_user: unapprover
+ ) do |service|
+ expect(service).to receive(:execute).with(merge_request, "unreviewed")
+ end
+
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unapprove", unapprover)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 2cf8872cd40..6000fa29dc4 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -229,7 +229,7 @@ RSpec.describe API::MergeRequests, :aggregate_failures, feature_category: :sourc
merge_request_closed.id,
merge_request.id
])
- expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at))
+ expect(json_response.last.keys).to match_array(%w[id iid title web_url created_at description project_id state updated_at])
expect(json_response.last['iid']).to eq(merge_request.iid)
expect(json_response.last['title']).to eq(merge_request.title)
expect(json_response.last).to have_key('web_url')
@@ -2175,7 +2175,7 @@ RSpec.describe API::MergeRequests, :aggregate_failures, feature_category: :sourc
expect(response).to have_gitlab_http_status(:created)
expect(json_response['title']).to eq('Test merge_request')
- expect(json_response['labels']).to eq(%w(label label2))
+ expect(json_response['labels']).to eq(%w[label label2])
expect(json_response['milestone']['id']).to eq(milestone.id)
expect(json_response['squash']).to be_truthy
expect(json_response['force_remove_source_branch']).to be_falsy
@@ -2187,11 +2187,11 @@ RSpec.describe API::MergeRequests, :aggregate_failures, feature_category: :sourc
end
it_behaves_like 'creates merge request with labels' do
- let(:labels) { %w(label label2) }
+ let(:labels) { %w[label label2] }
end
it_behaves_like 'creates merge request with labels' do
- let(:labels) { %w(label label2) }
+ let(:labels) { %w[label label2] }
end
it 'creates merge request with special label names' do
diff --git a/spec/requests/api/metadata_spec.rb b/spec/requests/api/metadata_spec.rb
index b81fe3f51b5..c8cee31db47 100644
--- a/spec/requests/api/metadata_spec.rb
+++ b/spec/requests/api/metadata_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe API::Metadata, feature_category: :shared do
let(:personal_access_token) { create(:personal_access_token, scopes: scopes) }
context 'with api scope' do
- let(:scopes) { %i(api) }
+ let(:scopes) { %i[api] }
it 'returns the metadata information' do
get api(endpoint, personal_access_token: personal_access_token)
@@ -42,7 +42,7 @@ RSpec.describe API::Metadata, feature_category: :shared do
end
context 'with ai_features scope' do
- let(:scopes) { %i(ai_features) }
+ let(:scopes) { %i[ai_features] }
it 'returns the metadata information' do
get api(endpoint, personal_access_token: personal_access_token)
@@ -58,7 +58,7 @@ RSpec.describe API::Metadata, feature_category: :shared do
end
context 'with read_user scope' do
- let(:scopes) { %i(read_user) }
+ let(:scopes) { %i[read_user] }
it 'returns the metadata information' do
get api(endpoint, personal_access_token: personal_access_token)
@@ -74,7 +74,7 @@ RSpec.describe API::Metadata, feature_category: :shared do
end
context 'with neither api, ai_features nor read_user scope' do
- let(:scopes) { %i(read_repository) }
+ let(:scopes) { %i[read_repository] }
it 'returns authorization error' do
get api(endpoint, personal_access_token: personal_access_token)
diff --git a/spec/requests/api/ml/mlflow/model_versions_spec.rb b/spec/requests/api/ml/mlflow/model_versions_spec.rb
new file mode 100644
index 00000000000..f59888ec70f
--- /dev/null
+++ b/spec/requests/api/ml/mlflow/model_versions_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ml::Mlflow::ModelVersions, feature_category: :mlops do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+ let_it_be(:another_project) { build(:project).tap { |p| p.add_developer(developer) } }
+
+ let_it_be(:name) { 'a-model-name' }
+ let_it_be(:version) { '0.0.1' }
+ let_it_be(:model) { create(:ml_models, project: project, name: name) }
+ let_it_be(:model_version) { create(:ml_model_versions, project: project, model: model, version: version) }
+
+ let_it_be(:tokens) do
+ {
+ write: create(:personal_access_token, scopes: %w[read_api api], user: developer),
+ read: create(:personal_access_token, scopes: %w[read_api], user: developer),
+ no_access: create(:personal_access_token, scopes: %w[read_user], user: developer),
+ different_user: create(:personal_access_token, scopes: %w[read_api api], user: build(:user))
+ }
+ end
+
+ let(:current_user) { developer }
+ let(:access_token) { tokens[:write] }
+ let(:headers) { { 'Authorization' => "Bearer #{access_token.token}" } }
+ let(:project_id) { project.id }
+ let(:default_params) { {} }
+ let(:params) { default_params }
+ let(:request) { get api(route), params: params, headers: headers }
+ let(:json_response) { Gitlab::Json.parse(api_response.body) }
+
+ subject(:api_response) do
+ request
+ response
+ end
+
+ describe 'GET /projects/:id/ml/mlflow/api/2.0/mlflow/model_versions/get' do
+ let(:route) do
+ "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/model_versions/get?name=#{name}&version=#{version}"
+ end
+
+ it 'returns the model version', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:ok)
+ expect(json_response['model_version']).not_to be_nil
+ expect(json_response['model_version']['name']).to eq(name)
+ expect(json_response['model_version']['version']).to eq(version)
+ end
+
+ describe 'Error States' do
+ context 'when has access' do
+ context 'and model name in incorrect' do
+ let(:route) do
+ "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/model_versions/get?name=--&version=#{version}"
+ end
+
+ it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'
+ end
+
+ context 'and version in incorrect' do
+ let(:route) do
+ "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/model_versions/get?name=#{name}&version=--"
+ end
+
+ it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'
+ end
+
+ context 'when user lacks read_model_registry rights' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(current_user, :read_model_registry, project)
+ .and_return(false)
+ end
+
+ it "is Not Found" do
+ is_expected.to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ it_behaves_like 'MLflow|shared model registry error cases'
+ it_behaves_like 'MLflow|Requires read_api scope'
+ end
+ end
+end
diff --git a/spec/requests/api/ml/mlflow/registered_models_spec.rb b/spec/requests/api/ml/mlflow/registered_models_spec.rb
new file mode 100644
index 00000000000..cd8b0a53ef3
--- /dev/null
+++ b/spec/requests/api/ml/mlflow/registered_models_spec.rb
@@ -0,0 +1,203 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ml::Mlflow::RegisteredModels, feature_category: :mlops do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+ let_it_be(:model) do
+ create(:ml_models, :with_metadata, project: project)
+ end
+
+ let_it_be(:tokens) do
+ {
+ write: create(:personal_access_token, scopes: %w[read_api api], user: developer),
+ read: create(:personal_access_token, scopes: %w[read_api], user: developer),
+ no_access: create(:personal_access_token, scopes: %w[read_user], user: developer),
+ different_user: create(:personal_access_token, scopes: %w[read_api api], user: build(:user))
+ }
+ end
+
+ let(:current_user) { developer }
+ let(:access_token) { tokens[:write] }
+ let(:headers) { { 'Authorization' => "Bearer #{access_token.token}" } }
+ let(:project_id) { project.id }
+ let(:default_params) { {} }
+ let(:params) { default_params }
+ let(:request) { get api(route), params: params, headers: headers }
+ let(:json_response) { Gitlab::Json.parse(api_response.body) }
+
+ subject(:api_response) do
+ request
+ response
+ end
+
+ describe 'GET /projects/:id/ml/mlflow/api/2.0/mlflow/registered-models/get' do
+ let(:model_name) { model.name }
+ let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/registered-models/get?name=#{model_name}" }
+
+ it 'returns the model', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:ok)
+ is_expected.to match_response_schema('ml/get_model')
+ end
+
+ describe 'Error States' do
+ context 'when has access' do
+ context 'and model does not exist' do
+ let(:model_name) { 'foo' }
+
+ it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'
+ end
+
+ context 'and name is not passed' do
+ let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/registered-models/get" }
+
+ it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'
+ end
+ end
+
+ it_behaves_like 'MLflow|shared model registry error cases'
+ it_behaves_like 'MLflow|Requires read_api scope'
+ end
+ end
+
+ describe 'POST /projects/:id/ml/mlflow/api/2.0/mlflow/registered-models/create' do
+ let(:route) do
+ "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/registered-models/create"
+ end
+
+ let(:params) { { name: 'my-model-name' } }
+ let(:request) { post api(route), params: params, headers: headers }
+
+ it 'creates the model', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:ok)
+ expect(json_response).to include('registered_model')
+ end
+
+ describe 'Error States' do
+ context 'when the model name is not passed' do
+ let(:params) { {} }
+
+ it_behaves_like 'MLflow|Bad Request'
+ end
+
+ context 'when the model name already exists' do
+ let(:existing_model) do
+ create(:ml_models, user: current_user, project: project)
+ end
+
+ let(:params) { { name: existing_model.name } }
+
+ it "is Bad Request", :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:bad_request)
+
+ expect(json_response).to include({ 'error_code' => 'RESOURCE_ALREADY_EXISTS' })
+ end
+ end
+
+ context 'when project does not exist' do
+ let(:route) { "/projects/#{non_existing_record_id}/ml/mlflow/api/2.0/mlflow/registered-models/create" }
+
+ it "is Not Found", :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:not_found)
+
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+ end
+
+ # TODO: Ensure consisted error responses https://gitlab.com/gitlab-org/gitlab/-/issues/429731
+ context 'when a duplicate tag name is supplied' do
+ let(:params) do
+ { name: 'my-model-name', tags: [{ key: 'key1', value: 'value1' }, { key: 'key1', value: 'value2' }] }
+ end
+
+ it "creates the model with only the second tag", :aggregate_failures do
+ expect(json_response).to include({ 'error_code' => 'RESOURCE_ALREADY_EXISTS' })
+ end
+ end
+
+ # TODO: Ensure consisted error responses https://gitlab.com/gitlab-org/gitlab/-/issues/429731
+ context 'when an empty tag name is supplied' do
+ let(:params) do
+ { name: 'my-model-name', tags: [{ key: '', value: 'value1' }, { key: 'key1', value: 'value2' }] }
+ end
+
+ it "creates the model with only the second tag", :aggregate_failures do
+ expect(json_response).to include({ 'error_code' => 'RESOURCE_ALREADY_EXISTS' })
+ end
+ end
+
+ it_behaves_like 'MLflow|shared model registry error cases'
+ it_behaves_like 'MLflow|Requires api scope and write permission'
+ end
+ end
+
+ describe 'PATCH /projects/:id/ml/mlflow/api/2.0/mlflow/registered-models/update' do
+ let(:model_name) { model.name }
+ let(:model_description) { 'updated model description' }
+ let(:params) { { name: model_name, description: model_description } }
+ let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/registered-models/update" }
+ let(:request) { patch api(route), params: params, headers: headers }
+
+ it 'returns the updated model', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:ok)
+ is_expected.to match_response_schema('ml/update_model')
+ expect(json_response["registered_model"]["description"]).to eq(model_description)
+ end
+
+ describe 'Error States' do
+ context 'when has access' do
+ context 'and model does not exist' do
+ let(:model_name) { 'foo' }
+
+ it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'
+ end
+
+ context 'and name is not passed' do
+ let(:params) { { description: model_description } }
+
+ it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'
+ end
+ end
+
+ it_behaves_like 'MLflow|shared model registry error cases'
+ it_behaves_like 'MLflow|Requires api scope and write permission'
+ end
+ end
+
+ describe 'POST /projects/:id/ml/mlflow/api/2.0/mlflow/registered-models/get-latest-versions' do
+ let_it_be(:version1) { create(:ml_model_versions, model: model, created_at: 1.week.ago) }
+ let_it_be(:version2) { create(:ml_model_versions, model: model, created_at: 1.day.ago) }
+
+ let(:model_name) { model.name }
+ let(:params) { { name: model_name } }
+ let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/registered-models/get-latest-versions" }
+ let(:request) { post api(route), params: params, headers: headers }
+
+ it 'returns an array with the most recently created model version', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:ok)
+ is_expected.to match_response_schema('ml/get_latest_versions')
+ expect(json_response["model_versions"][0]["name"]).to eq(model_name)
+ expect(json_response["model_versions"][0]["version"]).to eq(version2.version)
+ end
+
+ describe 'Error States' do
+ context 'when has access' do
+ context 'and model does not exist' do
+ let(:model_name) { 'foo' }
+
+ it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'
+ end
+
+ context 'and name is not passed' do
+ let(:params) { {} }
+
+ it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'
+ end
+ end
+
+ it_behaves_like 'MLflow|shared model registry error cases'
+ it_behaves_like 'MLflow|Requires read_api scope'
+ end
+ end
+end
diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb
index 340420e46e0..b5f38698857 100644
--- a/spec/requests/api/npm_project_packages_spec.rb
+++ b/spec/requests/api/npm_project_packages_spec.rb
@@ -196,7 +196,7 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
context 'with a job token for a different user' do
let_it_be(:other_user) { create(:user) }
- let_it_be_with_reload(:other_job) { create(:ci_build, :running, user: other_user) }
+ let_it_be_with_reload(:other_job) { create(:ci_build, :running, user: other_user, project: project) }
let(:headers) { build_token_auth_header(other_job.token) }
@@ -245,7 +245,7 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
context 'with a job token for a different user' do
let_it_be(:other_user) { create(:user) }
- let_it_be_with_reload(:other_job) { create(:ci_build, :running, user: other_user) }
+ let_it_be_with_reload(:other_job) { create(:ci_build, :running, user: other_user, project: project) }
let(:headers) { build_token_auth_header(other_job.token) }
diff --git a/spec/requests/api/pages/pages_spec.rb b/spec/requests/api/pages/pages_spec.rb
index aa1869eaa84..74dd1ed4f2b 100644
--- a/spec/requests/api/pages/pages_spec.rb
+++ b/spec/requests/api/pages/pages_spec.rb
@@ -9,7 +9,6 @@ RSpec.describe API::Pages, feature_category: :pages do
before do
project.add_maintainer(user)
- project.mark_pages_as_deployed
end
describe 'DELETE /projects/:id/pages' do
@@ -17,7 +16,7 @@ RSpec.describe API::Pages, feature_category: :pages do
it_behaves_like 'DELETE request permissions for admin mode' do
before do
- allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
+ stub_pages_setting(enabled: true)
end
let(:succes_status_code) { :no_content }
@@ -25,7 +24,7 @@ RSpec.describe API::Pages, feature_category: :pages do
context 'when Pages is disabled' do
before do
- allow(Gitlab.config.pages).to receive(:enabled).and_return(false)
+ stub_pages_setting(enabled: false)
end
it_behaves_like '404 response' do
@@ -35,7 +34,7 @@ RSpec.describe API::Pages, feature_category: :pages do
context 'when Pages is enabled' do
before do
- allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
+ stub_pages_setting(enabled: true)
end
context 'when Pages are deployed' do
@@ -48,15 +47,11 @@ RSpec.describe API::Pages, feature_category: :pages do
it 'removes the pages' do
delete api(path, admin, admin_mode: true)
- expect(project.reload.pages_metadatum.deployed?).to be(false)
+ expect(project.reload.pages_deployed?).to be(false)
end
end
context 'when pages are not deployed' do
- before do
- project.mark_pages_as_not_deployed
- end
-
it 'returns 204' do
delete api(path, admin, admin_mode: true)
diff --git a/spec/requests/api/personal_access_tokens_spec.rb b/spec/requests/api/personal_access_tokens_spec.rb
index 166768ea605..a1d29c4a935 100644
--- a/spec/requests/api/personal_access_tokens_spec.rb
+++ b/spec/requests/api/personal_access_tokens_spec.rb
@@ -461,6 +461,18 @@ RSpec.describe API::PersonalAccessTokens, :aggregate_failures, feature_category:
expect(json_response['expires_at']).to eq((Date.today + 1.week).to_s)
end
+ context 'when expiry is defined' do
+ it "rotates user's own token", :freeze_time do
+ expiry_date = Date.today + 1.month
+
+ post(api(path, token.user), params: { expires_at: expiry_date })
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['token']).not_to eq(token.token)
+ expect(json_response['expires_at']).to eq(expiry_date.to_s)
+ end
+ end
+
context 'without permission' do
it 'returns an error message' do
another_user = create(:user)
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index ec98df22af7..165ea7bf66e 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -98,6 +98,7 @@ ci_cd_settings:
- merge_pipelines_enabled
- auto_rollback_enabled
- inbound_job_token_scope_enabled
+ - restrict_pipeline_cancellation_role
remapped_attributes:
default_git_depth: ci_default_git_depth
forward_deployment_enabled: ci_forward_deployment_enabled
diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb
index f51b94bb78e..7797e8e9402 100644
--- a/spec/requests/api/project_container_repositories_spec.rb
+++ b/spec/requests/api/project_container_repositories_spec.rb
@@ -168,7 +168,7 @@ RSpec.describe API::ProjectContainerRepositories, feature_category: :container_r
let(:api_user) { reporter }
before do
- stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest))
+ stub_container_registry_tags(repository: root_repository.path, tags: %w[rootA latest])
end
it_behaves_like 'a package tracking event', described_class.name, 'list_tags'
@@ -177,7 +177,7 @@ RSpec.describe API::ProjectContainerRepositories, feature_category: :container_r
subject
expect(json_response.length).to eq(2)
- expect(json_response.map { |repository| repository['name'] }).to eq %w(latest rootA)
+ expect(json_response.map { |repository| repository['name'] }).to eq %w[latest rootA]
end
it 'returns a matching schema' do
@@ -362,7 +362,7 @@ RSpec.describe API::ProjectContainerRepositories, feature_category: :container_r
let(:api_user) { reporter }
before do
- stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
+ stub_container_registry_tags(repository: root_repository.path, tags: %w[rootA], with_manifest: true)
end
it 'returns a details of tag' do
@@ -408,7 +408,7 @@ RSpec.describe API::ProjectContainerRepositories, feature_category: :container_r
context 'when there are multiple tags' do
before do
- stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA rootB), with_manifest: true)
+ stub_container_registry_tags(repository: root_repository.path, tags: %w[rootA rootB], with_manifest: true)
end
it 'properly removes tag' do
@@ -427,7 +427,7 @@ RSpec.describe API::ProjectContainerRepositories, feature_category: :container_r
context 'when there\'s only one tag' do
before do
- stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
+ stub_container_registry_tags(repository: root_repository.path, tags: %w[rootA], with_manifest: true)
end
it 'properly removes tag' do
diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb
index 219c748c9a6..2ac9a7d97f1 100644
--- a/spec/requests/api/project_packages_spec.rb
+++ b/spec/requests/api/project_packages_spec.rb
@@ -197,6 +197,26 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
end
end
+ context 'filtering on package_version' do
+ include_context 'package filter context'
+
+ it 'returns the versioned package' do
+ url = package_filter_url(:version, '2.0.4')
+ get api(url, user)
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['version']).to eq(package2.version)
+ end
+
+ it 'include_versionless has no effect' do
+ url = "/projects/#{project.id}/packages?package_version=2.0.4&include_versionless=true"
+ get api(url, user)
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['version']).to eq(package2.version)
+ end
+ end
+
it_behaves_like 'with versionless packages'
it_behaves_like 'with status param'
it_behaves_like 'does not cause n^2 queries'
diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb
index 96ed3042d00..7b5dc0d5ef8 100644
--- a/spec/requests/api/project_repository_storage_moves_spec.rb
+++ b/spec/requests/api/project_repository_storage_moves_spec.rb
@@ -8,5 +8,29 @@ RSpec.describe API::ProjectRepositoryStorageMoves, feature_category: :gitaly do
let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, container: container) }
let(:repository_storage_move_factory) { :project_repository_storage_move }
let(:bulk_worker_klass) { Projects::ScheduleBulkRepositoryShardMovesWorker }
+
+ context 'when project is hidden' do
+ let_it_be(:container) { create(:project, :hidden) }
+ let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, container: container) }
+
+ it_behaves_like 'get single container repository storage move' do
+ let(:container_id) { container.id }
+ let(:url) { "/projects/#{container_id}/repository_storage_moves/#{repository_storage_move_id}" }
+ end
+
+ it_behaves_like 'post single container repository storage move'
+ end
+
+ context 'when project is pending delete' do
+ let_it_be(:container) { create(:project, pending_delete: true) }
+ let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, container: container) }
+
+ it_behaves_like 'get single container repository storage move' do
+ let(:container_id) { container.id }
+ let(:url) { "/projects/#{container_id}/repository_storage_moves/#{repository_storage_move_id}" }
+ end
+
+ it_behaves_like 'post single container repository storage move'
+ end
end
end
diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb
index e1d156afd54..1987d70633b 100644
--- a/spec/requests/api/project_templates_spec.rb
+++ b/spec/requests/api/project_templates_spec.rb
@@ -69,7 +69,7 @@ RSpec.describe API::ProjectTemplates, feature_category: :source_code_management
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to match_response_schema('public_api/v4/template_list')
- expect(json_response.map { |t| t['key'] }).to match_array(%w(bug feature_proposal template_test))
+ expect(json_response.map { |t| t['key'] }).to match_array(%w[bug feature_proposal template_test])
end
it 'returns merge request templates' do
@@ -78,7 +78,7 @@ RSpec.describe API::ProjectTemplates, feature_category: :source_code_management
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(response).to match_response_schema('public_api/v4/template_list')
- expect(json_response.map { |t| t['key'] }).to match_array(%w(bug feature_proposal template_test))
+ expect(json_response.map { |t| t['key'] }).to match_array(%w[bug feature_proposal template_test])
end
it 'returns 400 for an unknown template type' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 64e010aa50f..e9319d514aa 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -286,6 +286,32 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
expect(json_response.map { |p| p['id'] }).not_to include(project.id)
end
+ context 'when user requests pending_delete projects' do
+ before do
+ project.update!(pending_delete: true)
+ end
+
+ let(:params) { { include_pending_delete: true } }
+
+ it 'does not return projects marked for deletion' do
+ get api(path, user), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).not_to include(project.id)
+ end
+
+ context 'when user is an admin' do
+ it 'returns projects marked for deletion' do
+ get api(path, admin, admin_mode: true), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an Array
+ expect(json_response.map { |p| p['id'] }).to include(project.id)
+ end
+ end
+ end
+
it 'does not include open_issues_count if issues are disabled' do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
@@ -299,7 +325,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
context 'filter by topic (column topic_list)' do
before do
- project.update!(topic_list: %w(ruby javascript))
+ project.update!(topic_list: %w[ruby javascript])
end
it 'returns no projects' do
@@ -868,7 +894,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
context 'sorting' do
context 'by project statistics' do
- %w(repository_size storage_size wiki_size packages_size).each do |order_by|
+ %w[repository_size storage_size wiki_size packages_size].each do |order_by|
context "sorting by #{order_by}" do
before do
ProjectStatistics.update_all(order_by => 100)
@@ -1400,13 +1426,14 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
it 'disallows creating a project with an import_url that is not reachable' do
url = 'http://example.com'
endpoint_url = "#{url}/info/refs?service=git-upload-pack"
- stub_full_request(endpoint_url, method: :get).to_return({ status: 301, body: '', headers: nil })
+ error_response = { status: 301, body: '', headers: nil }
+ stub_full_request(endpoint_url, method: :get).to_return(error_response)
project_params = { import_url: url, path: 'path-project-Foo', name: 'Foo Project' }
expect { post api(path, user), params: project_params }.not_to change { Project.count }
expect(response).to have_gitlab_http_status(:unprocessable_entity)
- expect(json_response['message']).to eq("#{url} is not a valid HTTP Git repository")
+ expect(json_response['message']).to eq("#{url} endpoint error: #{error_response[:status]}")
end
it 'creates a project with an import_url that is valid' do
@@ -2533,7 +2560,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
context 'and the project has a private repository' do
let(:project) { create(:project, :repository, :public, :repository_private) }
- let(:protected_attributes) { %w(default_branch ci_config_path) }
+ let(:protected_attributes) { %w[default_branch ci_config_path] }
it 'hides protected attributes of private repositories if user is not a member' do
get api(path, user)
@@ -2782,10 +2809,20 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
expect(json_response['shared_with_groups'][0]['expires_at']).to eq(expires_at.to_s)
end
- it 'returns a project by path name' do
- get api(path, user)
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['name']).to eq(project.name)
+ context 'when path name is specified' do
+ it 'returns a project' do
+ get api("/projects/#{CGI.escape(project.full_path)}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq(project.name)
+ end
+
+ it 'returns a project using case-insensitive search' do
+ get api("/projects/#{CGI.escape(project.full_path.swapcase)}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq(project.name)
+ end
end
context 'when a project is moved' do
@@ -3688,6 +3725,16 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
it_behaves_like '412 response' do
subject(:request) { api("/projects/#{project.id}/share/#{group.id}", user) }
end
+
+ it "returns an error when link is not destroyed" do
+ allow(::Projects::GroupLinks::DestroyService).to receive_message_chain(:new, :execute)
+ .and_return(ServiceResponse.error(message: '404 Not Found', reason: :not_found))
+
+ delete api("/projects/#{project.id}/share/#{group.id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq '404 Not Found'
+ end
end
it 'returns a 400 when group id is not an integer' do
@@ -3893,7 +3940,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
expect(Project.find_by(path: project[:path]).analytics_access_level).to eq(ProjectFeature::PRIVATE)
end
- %i(releases_access_level environments_access_level feature_flags_access_level infrastructure_access_level monitor_access_level model_experiments_access_level).each do |field|
+ %i[releases_access_level environments_access_level feature_flags_access_level infrastructure_access_level monitor_access_level model_experiments_access_level].each do |field|
it "sets #{field}" do
put api(path, user), params: { field => 'private' }
diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb
index 0b2641b062c..9305155d285 100644
--- a/spec/requests/api/pypi_packages_spec.rb
+++ b/spec/requests/api/pypi_packages_spec.rb
@@ -207,7 +207,22 @@ RSpec.describe API::PypiPackages, feature_category: :package_registry do
let(:url) { "/projects/#{project.id}/packages/pypi" }
let(:headers) { {} }
let(:requires_python) { '>=3.7' }
- let(:base_params) { { requires_python: requires_python, version: '1.0.0', name: 'sample-project', sha256_digest: '1' * 64, md5_digest: '1' * 32 } }
+ let(:base_params) do
+ {
+ requires_python: requires_python,
+ version: '1.0.0',
+ name: 'sample-project',
+ sha256_digest: '1' * 64,
+ md5_digest: '1' * 32,
+ metadata_version: '2.3',
+ author_email: 'cschultz@example.com, snoopy@peanuts.com',
+ description: 'Example description',
+ description_content_type: 'text/plain',
+ summary: 'A module for collecting votes from beagles.',
+ keywords: 'dog,puppy,voting,election'
+ }
+ end
+
let(:params) { base_params.merge(content: temp_file(file_name)) }
let(:send_rewritten_field) { true }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_pypi_user' } }
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index a018b91019b..493dc4e72c6 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -1492,7 +1492,7 @@ RSpec.describe API::Releases, :aggregate_failures, feature_category: :release_or
subject
expect(response).to have_gitlab_http_status(:ok)
- expect(returned_milestones).to match_array(%w(milestone2 milestone3))
+ expect(returned_milestones).to match_array(%w[milestone2 milestone3])
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 22239f1d23f..f38a120cc74 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -742,7 +742,7 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do
describe 'GET :id/repository/merge_base' do
let(:refs) do
- %w(304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209 570e7b2abdd848b95f2f578043fc23bd6f6fd24d)
+ %w[304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209 570e7b2abdd848b95f2f578043fc23bd6f6fd24d]
end
subject(:request) do
@@ -786,7 +786,7 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do
context 'when passing refs that do not exist' do
it_behaves_like '400 response' do
- let(:refs) { %w(304d257dcb821665ab5110318fc58a007bd104ed missing) }
+ let(:refs) { %w[304d257dcb821665ab5110318fc58a007bd104ed missing] }
let(:current_user) { user }
let(:message) { 'Could not find ref: missing' }
end
@@ -801,7 +801,7 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do
end
context 'when not enough refs are passed' do
- let(:refs) { %w(only-one) }
+ let(:refs) { %w[only-one] }
let(:current_user) { user }
it 'renders a bad request error' do
diff --git a/spec/requests/api/resource_access_tokens_spec.rb b/spec/requests/api/resource_access_tokens_spec.rb
index dcb6572d413..01e02651a64 100644
--- a/spec/requests/api/resource_access_tokens_spec.rb
+++ b/spec/requests/api/resource_access_tokens_spec.rb
@@ -477,6 +477,7 @@ RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do
let_it_be(:token) { create(:personal_access_token, user: project_bot) }
let_it_be(:resource_id) { resource.id }
let_it_be(:token_id) { token.id }
+ let(:params) { {} }
let(:path) { "/#{source_type}s/#{resource_id}/access_tokens/#{token_id}/rotate" }
@@ -485,7 +486,7 @@ RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do
resource.add_owner(user)
end
- subject(:rotate_token) { post api(path, user) }
+ subject(:rotate_token) { post(api(path, user), params: params) }
it "allows owner to rotate token", :freeze_time do
rotate_token
@@ -495,6 +496,19 @@ RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do
expect(json_response['expires_at']).to eq((Date.today + 1.week).to_s)
end
+ context 'when expiry is defined' do
+ let(:expiry_date) { Date.today + 1.month }
+ let(:params) { { expires_at: expiry_date } }
+
+ it "allows owner to rotate token", :freeze_time do
+ rotate_token
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['token']).not_to eq(token.token)
+ expect(json_response['expires_at']).to eq(expiry_date.to_s)
+ end
+ end
+
context 'without permission' do
it 'returns an error message' do
another_user = create(:user)
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 6a57cf52466..4733fdafbfb 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -140,14 +140,14 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
end
context 'when DB timeouts occur from global searches', :aggregate_failures do
- %w(
+ %w[
issues
merge_requests
milestones
projects
snippet_titles
users
- ).each do |scope|
+ ].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' }
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 2fdcf710471..5656fda7684 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -79,7 +79,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
expect(json_response['slack_app_secret']).to be_nil
expect(json_response['slack_app_signing_secret']).to be_nil
expect(json_response['slack_app_verification_token']).to be_nil
- expect(json_response['valid_runner_registrars']).to match_array(%w(project group))
+ expect(json_response['valid_runner_registrars']).to match_array(%w[project group])
expect(json_response['ci_max_includes']).to eq(150)
expect(json_response['allow_account_deletion']).to eq(true)
expect(json_response['gitlab_shell_operation_limit']).to eq(600)
@@ -261,7 +261,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
expect(json_response['max_decompressed_archive_size']).to eq(20000)
expect(json_response['max_terraform_state_size_bytes']).to eq(1_000)
expect(json_response['disabled_oauth_sign_in_sources']).to eq([])
- expect(json_response['import_sources']).to match_array(%w(github bitbucket))
+ expect(json_response['import_sources']).to match_array(%w[github bitbucket])
expect(json_response['wiki_page_max_content_bytes']).to eq(12345)
expect(json_response['wiki_asciidoc_allow_uri_includes']).to be(true)
expect(json_response['personal_access_token_prefix']).to eq("GL-")
@@ -418,12 +418,12 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
end
it 'does not allow unrestricted key lengths' do
- types = %w(dsa_key_restriction
+ types = %w[dsa_key_restriction
ecdsa_key_restriction
ecdsa_sk_key_restriction
ed25519_key_restriction
ed25519_sk_key_restriction
- rsa_key_restriction)
+ rsa_key_restriction]
types.each do |type|
put api("/application/settings", admin), params: { type => 0 }
@@ -519,7 +519,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
context 'EKS integration settings' do
let(:attribute_names) { settings.keys.map(&:to_s) }
- let(:sensitive_attributes) { %w(eks_secret_access_key) }
+ let(:sensitive_attributes) { %w[eks_secret_access_key] }
let(:exposed_attributes) { attribute_names - sensitive_attributes }
let(:settings) do
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 604631bbf7f..6bbd43bfc14 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -206,7 +206,7 @@ RSpec.describe API::Tags, feature_category: :source_code_management do
expect(response).to match_response_schema('public_api/v4/tags')
expect(response.headers).to include('Link')
tag_names = json_response.map { |x| x['name'] }
- expect(tag_names).to match_array(%w(v1.1.0 v1.1.1))
+ expect(tag_names).to match_array(%w[v1.1.0 v1.1.1])
end
end
diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb
index c46d6954da3..ae48b0c18cd 100644
--- a/spec/requests/api/task_completion_status_spec.rb
+++ b/spec/requests/api/task_completion_status_spec.rb
@@ -21,30 +21,30 @@ RSpec.describe 'task completion status response', features: :team_planning do
expected_completed_count: 0
},
{
- description: %{- [ ] task 1
- - [x] task 2 },
+ description: %(- [ ] task 1
+ - [x] task 2 ),
expected_count: 2,
expected_completed_count: 1
},
{
- description: %{- [ ] task 1
- - [ ] task 2 },
+ description: %(- [ ] task 1
+ - [ ] task 2 ),
expected_count: 2,
expected_completed_count: 0
},
{
- description: %{- [x] task 1
- - [x] task 2 },
+ description: %(- [x] task 1
+ - [x] task 2 ),
expected_count: 2,
expected_completed_count: 2
},
{
- description: %{- [ ] task 1},
+ description: %(- [ ] task 1),
expected_count: 1,
expected_completed_count: 0
},
{
- description: %{- [x] task 1},
+ description: %(- [x] task 1),
expected_count: 1,
expected_completed_count: 1
}
diff --git a/spec/requests/api/unleash_spec.rb b/spec/requests/api/unleash_spec.rb
index 75b26b98228..050be3ae8aa 100644
--- a/spec/requests/api/unleash_spec.rb
+++ b/spec/requests/api/unleash_spec.rb
@@ -96,7 +96,7 @@ RSpec.describe API::Unleash, feature_category: :feature_flags do
end
end
- %w(/feature_flags/unleash/:project_id/features /feature_flags/unleash/:project_id/client/features).each do |features_endpoint|
+ %w[/feature_flags/unleash/:project_id/features /feature_flags/unleash/:project_id/client/features].each do |features_endpoint|
describe "GET #{features_endpoint}", :use_clean_rails_redis_caching do
let(:features_url) { features_endpoint.sub(':project_id', project_id.to_s) }
let(:client) { create(:operations_feature_flags_client, project: project) }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 7da44266064..76fe72efc64 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile do
include WorkhorseHelpers
include KeysetPaginationHelpers
+ include CryptoHelpers
let_it_be(:admin) { create(:admin) }
let_it_be(:user, reload: true) { create(:user, username: 'user.withdot') }
@@ -226,6 +227,19 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
end
+ context 'with search parameter' do
+ let_it_be(:first_user) { create(:user, username: 'a-user') }
+ let_it_be(:second_user) { create(:user, username: 'a-user2') }
+
+ it 'prioritizes username match' do
+ get api(path, user, admin_mode: true), params: { search: first_user.username }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first['username']).to eq('a-user')
+ expect(json_response.second['username']).to eq('a-user2')
+ end
+ end
+
context 'N+1 queries' do
before do
create_list(:user, 2)
@@ -1983,17 +1997,23 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
end
describe "PUT /user/:id/credit_card_validation" do
- let(:credit_card_validated_time) { Time.utc(2020, 1, 1) }
+ let(:network) { 'American Express' }
+ let(:holder_name) { 'John Smith' }
+ let(:last_digits) { '1111' }
let(:expiration_year) { Date.today.year + 10 }
+ let(:expiration_month) { 1 }
+ let(:expiration_date) { Date.new(expiration_year, expiration_month, -1) }
+ let(:credit_card_validated_at) { Time.utc(2020, 1, 1) }
+
let(:path) { "/user/#{user.id}/credit_card_validation" }
let(:params) do
{
- credit_card_validated_at: credit_card_validated_time,
+ credit_card_validated_at: credit_card_validated_at,
credit_card_expiration_year: expiration_year,
- credit_card_expiration_month: 1,
- credit_card_holder_name: 'John Smith',
- credit_card_type: 'AmericanExpress',
- credit_card_mask_number: '1111'
+ credit_card_expiration_month: expiration_month,
+ credit_card_holder_name: holder_name,
+ credit_card_type: network,
+ credit_card_mask_number: last_digits
}
end
@@ -2023,11 +2043,11 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
expect(response).to have_gitlab_http_status(:ok)
expect(user.credit_card_validation).to have_attributes(
- credit_card_validated_at: credit_card_validated_time,
- expiration_date: Date.new(expiration_year, 1, 31),
- last_digits: 1111,
- network: 'AmericanExpress',
- holder_name: 'John Smith'
+ credit_card_validated_at: credit_card_validated_at,
+ network_hash: sha256(network.downcase),
+ holder_name_hash: sha256(holder_name.downcase),
+ last_digits_hash: sha256(last_digits),
+ expiration_date_hash: sha256(expiration_date.to_s)
)
end
@@ -4519,7 +4539,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
describe 'POST /users/:user_id/personal_access_tokens' do
let(:name) { 'new pat' }
let(:expires_at) { 3.days.from_now.to_date.to_s }
- let(:scopes) { %w(api read_user) }
+ let(:scopes) { %w[api read_user] }
let(:path) { "/users/#{user.id}/personal_access_tokens" }
let(:params) { { name: name, scopes: scopes, expires_at: expires_at } }
diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb
deleted file mode 100644
index fbda291e901..00000000000
--- a/spec/requests/api/v3/github_spec.rb
+++ /dev/null
@@ -1,721 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::V3::Github, :aggregate_failures, feature_category: :integrations do
- let_it_be(:user) { create(:user) }
- let_it_be(:unauthorized_user) { create(:user) }
- let_it_be(:admin) { create(:user, :admin) }
- let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
-
- before do
- project.add_maintainer(user) if user
- end
-
- describe 'GET /orgs/:namespace/repos' do
- let_it_be(:group) { create(:group) }
-
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject do
- jira_get v3_api("/orgs/#{group.path}/repos", user)
- end
- end
-
- it 'logs when the endpoint is hit and `jira_dvcs_end_of_life_amnesty` is enabled' do
- expect(Gitlab::JsonLogger).to receive(:info).with(
- including(
- namespace: group.path,
- user_id: user.id,
- message: 'Deprecated Jira DVCS endpoint request'
- )
- )
-
- jira_get v3_api("/orgs/#{group.path}/repos", user)
-
- stub_feature_flags(jira_dvcs_end_of_life_amnesty: false)
-
- expect(Gitlab::JsonLogger).not_to receive(:info)
-
- jira_get v3_api("/orgs/#{group.path}/repos", user)
- end
-
- it 'returns an empty array' do
- jira_get v3_api("/orgs/#{group.path}/repos", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq([])
- end
-
- it 'returns 200 when namespace path include a dot' do
- group = create(:group, path: 'foo.bar')
-
- jira_get v3_api("/orgs/#{group.path}/repos", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- describe 'GET /user/repos' do
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject { jira_get v3_api('/user/repos', user) }
- end
-
- it 'returns an empty array' do
- jira_get v3_api('/user/repos', user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq([])
- end
- end
-
- shared_examples_for 'Jira-specific mimicked GitHub endpoints' do
- describe 'GET /.../issues/:id/comments' do
- let(:merge_request) do
- create(:merge_request, source_project: project, target_project: project)
- end
-
- let!(:note) do
- create(:note, project: project, noteable: merge_request)
- end
-
- context 'when user has access to the merge request' do
- it 'returns an array of notes' do
- jira_get v3_api("/repos/#{path}/issues/#{merge_request.id}/comments", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to be_an(Array)
- expect(json_response.size).to eq(1)
- end
- end
-
- context 'when user has no access to the merge request' do
- let(:project) { create(:project, :private) }
-
- before do
- project.add_guest(user)
- end
-
- it 'returns 404' do
- jira_get v3_api("/repos/#{path}/issues/#{merge_request.id}/comments", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- describe 'GET /.../pulls/:id/commits' do
- it 'returns an empty array' do
- jira_get v3_api("/repos/#{path}/pulls/xpto/commits", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq([])
- end
- end
-
- describe 'GET /.../pulls/:id/comments' do
- it 'returns an empty array' do
- jira_get v3_api("/repos/#{path}/pulls/xpto/comments", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq([])
- end
- end
- end
-
- # Here we test that using /-/jira as namespace/project still works,
- # since that is how old Jira setups will talk to us
- context 'old /-/jira endpoints' do
- it_behaves_like 'Jira-specific mimicked GitHub endpoints' do
- let(:path) { '-/jira' }
- end
-
- it 'returns an empty Array for events' do
- jira_get v3_api('/repos/-/jira/events', user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq([])
- end
- end
-
- context 'new :namespace/:project jira endpoints' do
- it_behaves_like 'Jira-specific mimicked GitHub endpoints' do
- let(:path) { "#{project.namespace.path}/#{project.path}" }
- end
-
- describe 'GET /users/:username' do
- let!(:user1) { create(:user, username: 'jane.porter') }
-
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject { jira_get v3_api("/users/#{user.username}", user) }
- end
-
- context 'user exists' do
- it 'responds with the expected user' do
- jira_get v3_api("/users/#{user.username}", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('entities/github/user')
- end
- end
-
- context 'user does not exist' do
- it 'responds with the expected status' do
- jira_get v3_api('/users/unknown_user_name', user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'no rights to request user lists' do
- before do
- expect(Ability).to receive(:allowed?).with(unauthorized_user, :read_users_list, :global).and_return(false)
- expect(Ability).to receive(:allowed?).at_least(:once).and_call_original
- end
-
- it 'responds with forbidden' do
- jira_get v3_api("/users/#{user.username}", unauthorized_user)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
- end
-
- describe 'GET events' do
- include ProjectForksHelper
-
- let(:group) { create(:group) }
- let(:project) { create(:project, :empty_repo, path: 'project.with.dot', group: group) }
- let(:events_path) { "/repos/#{group.path}/#{project.path}/events" }
-
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject { jira_get v3_api(events_path, user) }
- end
-
- context 'if there are no merge requests' do
- it 'returns an empty array' do
- jira_get v3_api(events_path, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to eq([])
- end
- end
-
- context 'if there is a merge request' do
- let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
-
- it 'returns an event' do
- jira_get v3_api(events_path, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to be_an(Array)
- expect(json_response.size).to eq(1)
- end
- end
-
- it 'avoids N+1 queries' do
- create(:merge_request, source_project: project)
- source_project = fork_project(project, nil, repository: true)
-
- control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { jira_get v3_api(events_path, user) }.count
-
- create_list(:merge_request, 2, :unique_branches, source_project: source_project, target_project: project)
-
- expect { jira_get v3_api(events_path, user) }.not_to exceed_all_query_limit(control_count)
- end
-
- context 'if there are more merge requests' do
- let!(:merge_request) { create(:merge_request, id: 10000, source_project: project, target_project: project, author: user) }
- let!(:merge_request2) { create(:merge_request, id: 10001, source_project: project, source_branch: generate(:branch), target_project: project, author: user) }
-
- it 'returns the expected amount of events' do
- jira_get v3_api(events_path, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to be_an(Array)
- expect(json_response.size).to eq(2)
- end
-
- it 'ensures each event has a unique id' do
- jira_get v3_api(events_path, user)
-
- ids = json_response.map { |event| event['id'] }.uniq
- expect(ids.size).to eq(2)
- end
- end
- end
- end
-
- describe 'repo pulls' do
- let_it_be(:project2) { create(:project, :repository, creator: user) }
- let_it_be(:assignee) { create(:user) }
- let_it_be(:assignee2) { create(:user) }
- let_it_be(:merge_request) do
- create(:merge_request, source_project: project, target_project: project, author: user, assignees: [assignee])
- end
-
- let_it_be(:merge_request_2) do
- create(:merge_request, source_project: project2, target_project: project2, author: user, assignees: [assignee, assignee2])
- end
-
- before do
- project2.add_maintainer(user)
- end
-
- def perform_request
- jira_get v3_api(route, user)
- end
-
- describe 'GET /-/jira/pulls' do
- let(:route) { '/repos/-/jira/pulls' }
-
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject { perform_request }
- end
-
- it 'returns an array of merge requests with github format' do
- perform_request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to be_an(Array)
- expect(json_response.size).to eq(2)
- expect(response).to match_response_schema('entities/github/pull_requests')
- end
-
- it 'returns multiple merge requests without N + 1' do
- perform_request
-
- control_count = ActiveRecord::QueryRecorder.new { perform_request }.count
-
- project3 = create(:project, :repository, creator: user)
- project3.add_maintainer(user)
- assignee3 = create(:user)
- create(:merge_request, source_project: project3, target_project: project3, author: user, assignees: [assignee3])
-
- expect { perform_request }.not_to exceed_query_limit(control_count)
- end
- end
-
- describe 'GET /repos/:namespace/:project/pulls' do
- let(:route) { "/repos/#{project.namespace.path}/#{project.path}/pulls" }
-
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject { perform_request }
- end
-
- it 'returns an array of merge requests for the proper project in github format' do
- perform_request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to be_an(Array)
- expect(json_response.size).to eq(1)
- expect(response).to match_response_schema('entities/github/pull_requests')
- end
-
- it 'returns multiple merge requests without N + 1' do
- perform_request
-
- control_count = ActiveRecord::QueryRecorder.new { perform_request }.count
-
- create(:merge_request, source_project: project, source_branch: 'fix')
-
- expect { perform_request }.not_to exceed_query_limit(control_count)
- end
- end
-
- describe 'GET /repos/:namespace/:project/pulls/:id' do
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject { jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/pulls/#{merge_request.id}", user) }
- end
-
- context 'when user has access to the merge requests' do
- it 'returns the requested merge request in github format' do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/pulls/#{merge_request.id}", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('entities/github/pull_request')
- end
- end
-
- context 'when user has no access to the merge request' do
- it 'returns 404' do
- project.add_guest(unauthorized_user)
-
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/pulls/#{merge_request.id}", unauthorized_user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when instance admin' do
- it 'returns the requested merge request in github format' do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/pulls/#{merge_request.id}", admin, admin_mode: true)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('entities/github/pull_request')
- end
- end
- end
- end
-
- describe 'GET /users/:namespace/repos' do
- let(:group) { create(:group, name: 'foo') }
-
- def expect_project_under_namespace(projects, namespace, user, admin_mode = false)
- jira_get v3_api("/users/#{namespace.path}/repos", user, admin_mode: admin_mode)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(response).to match_response_schema('entities/github/repositories')
-
- projects.each do |project|
- hash = json_response.find do |hash|
- hash['name'] == ::Gitlab::Jira::Dvcs.encode_project_name(project)
- end
-
- raise "Project #{project.full_path} not present in response" if hash.nil?
-
- expect(hash['owner']['login']).to eq(namespace.path)
- end
- expect(json_response.size).to eq(projects.size)
- end
-
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject { jira_get v3_api("/users/#{user.namespace.path}/repos", user) }
- end
-
- context 'group namespace' do
- let(:project) { create(:project, group: group) }
- let!(:project2) { create(:project, :public, group: group) }
-
- it 'returns an array of projects belonging to group excluding the ones user is not directly a member of, even when public' do
- expect_project_under_namespace([project], group, user)
- end
-
- context 'when instance admin' do
- let(:user) { create(:user, :admin) }
-
- it 'returns an array of projects belonging to group' do
- expect_project_under_namespace([project, project2], group, user, true)
- end
-
- context 'with a private group' do
- let(:group) { create(:group, :private) }
- let!(:project2) { create(:project, :private, group: group) }
-
- it 'returns an array of projects belonging to group' do
- expect_project_under_namespace([project, project2], group, user, true)
- end
- end
- end
- end
-
- context 'nested group namespace' do
- let(:group) { create(:group, :nested) }
- let!(:parent_group_project) { create(:project, group: group.parent, name: 'parent_group_project') }
- let!(:child_group_project) { create(:project, group: group, name: 'child_group_project') }
-
- before do
- group.parent.add_maintainer(user)
- end
-
- it 'returns an array of projects belonging to group with github format' do
- expect_project_under_namespace([parent_group_project, child_group_project], group.parent, user)
- end
-
- it 'avoids N+1 queries' do
- jira_get v3_api("/users/#{group.parent.path}/repos", user)
-
- control = ActiveRecord::QueryRecorder.new { jira_get v3_api("/users/#{group.parent.path}/repos", user) }
-
- new_group = create(:group, parent: group.parent)
- create(:project, :repository, group: new_group, creator: user)
-
- expect { jira_get v3_api("/users/#{group.parent.path}/repos", user) }.not_to exceed_query_limit(control)
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'user namespace' do
- let(:project) { create(:project, namespace: user.namespace) }
-
- it 'returns an array of projects belonging to user namespace with github format' do
- expect_project_under_namespace([project], user.namespace, user)
- end
- end
-
- context 'namespace path includes a dot' do
- let(:project) { create(:project, group: group) }
- let(:group) { create(:group, name: 'foo.bar') }
-
- before do
- group.add_maintainer(user)
- end
-
- it 'returns an array of projects belonging to group with github format' do
- expect_project_under_namespace([project], group, user)
- end
- end
-
- context 'unauthenticated' do
- it 'returns 401' do
- jira_get v3_api('/users/foo/repos', nil)
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
-
- context 'namespace does not exist' do
- it 'responds with not found status' do
- jira_get v3_api('/users/noo/repos', user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- describe 'GET /repos/:namespace/:project/branches' do
- context 'authenticated' do
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject { jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user) }
- end
-
- context 'updating project feature usage' do
- it 'counts Jira Cloud integration as enabled' do
- user_agent = 'Jira DVCS Connector Vertigo/4.42.0'
-
- freeze_time do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user), user_agent
-
- expect(project.reload.jira_dvcs_cloud_last_sync_at).to be_like_time(Time.now)
- end
- end
-
- it 'counts Jira Server integration as enabled' do
- user_agent = 'Jira DVCS Connector/3.2.4'
-
- freeze_time do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user), user_agent
-
- expect(project.reload.jira_dvcs_server_last_sync_at).to be_like_time(Time.now)
- end
- end
- end
-
- it 'returns an array of project branches with github format' do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an(Array)
-
- expect(response).to match_response_schema('entities/github/branches')
- end
-
- it 'returns 200 when project path include a dot' do
- project.update!(path: 'foo.bar')
-
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
-
- it 'returns 200 when namespace path include a dot' do
- group = create(:group, path: 'foo.bar')
- project = create(:project, :repository, group: group)
- project.add_reporter(user)
-
- jira_get v3_api("/repos/#{group.path}/#{project.path}/branches", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
-
- context 'when the project has no repository' do
- let_it_be(:project) { create(:project, creator: user) }
-
- it 'returns an empty collection response' do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to be_empty
- end
- end
- end
-
- context 'unauthenticated' do
- it 'returns 401' do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", nil)
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
-
- context 'unauthorized' do
- it 'returns 404 when lower access level' do
- project.add_guest(unauthorized_user)
-
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", unauthorized_user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- describe 'GET /repos/:namespace/:project/commits/:sha' do
- let(:commit) { project.repository.commit }
-
- def call_api(commit_id: commit.id)
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/commits/#{commit_id}", user)
- end
-
- def response_diff_files(response)
- Gitlab::Json.parse(response.body)['files']
- end
-
- context 'authenticated' do
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject { call_api }
- end
-
- it 'returns commit with github format' do
- call_api
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('entities/github/commit')
- end
-
- it 'returns 200 when project path include a dot' do
- project.update!(path: 'foo.bar')
-
- call_api
-
- expect(response).to have_gitlab_http_status(:ok)
- end
-
- context 'when namespace path includes a dot' do
- let(:group) { create(:group, path: 'foo.bar') }
- let(:project) { create(:project, :repository, group: group) }
-
- it 'returns 200 when namespace path include a dot' do
- project.add_reporter(user)
-
- call_api
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'when the Gitaly `CommitDiff` RPC times out', :use_clean_rails_memory_store_caching do
- let(:commit_diff_args) { [project.repository_storage, :diff_service, :commit_diff, any_args] }
-
- before do
- allow(Gitlab::GitalyClient).to receive(:call)
- .and_call_original
- end
-
- it 'handles the error, logs it, and returns empty diff files' do
- allow(Gitlab::GitalyClient).to receive(:call)
- .with(*commit_diff_args)
- .and_raise(GRPC::DeadlineExceeded)
-
- expect(Gitlab::ErrorTracking)
- .to receive(:track_exception)
- .with an_instance_of(GRPC::DeadlineExceeded)
-
- call_api
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response_diff_files(response)).to be_blank
- end
-
- it 'only calls Gitaly once for all attempts within a period of time' do
- expect(Gitlab::GitalyClient).to receive(:call)
- .with(*commit_diff_args)
- .once # <- once
- .and_raise(GRPC::DeadlineExceeded)
-
- 3.times do
- call_api
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response_diff_files(response)).to be_blank
- end
- end
-
- it 'calls Gitaly again after a period of time' do
- expect(Gitlab::GitalyClient).to receive(:call)
- .with(*commit_diff_args)
- .twice # <- twice
- .and_raise(GRPC::DeadlineExceeded)
-
- call_api
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response_diff_files(response)).to be_blank
-
- travel_to((described_class::GITALY_TIMEOUT_CACHE_EXPIRY + 1.second).from_now) do
- call_api
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response_diff_files(response)).to be_blank
- end
- end
-
- it 'uses a unique cache key, allowing other calls to succeed' do
- cache_key = [described_class::GITALY_TIMEOUT_CACHE_KEY, project.id, commit.cache_key].join(':')
- Rails.cache.write(cache_key, 1)
-
- expect(Gitlab::GitalyClient).to receive(:call)
- .with(*commit_diff_args)
- .once # <- once
-
- call_api
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response_diff_files(response)).to be_blank
-
- call_api(commit_id: commit.parent.id)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response_diff_files(response).length).to eq(1)
- end
- end
- end
-
- context 'unauthenticated' do
- let(:user) { nil }
-
- it 'returns 401' do
- call_api
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
-
- context 'unauthorized' do
- let(:user) { unauthorized_user }
-
- it 'returns 404 when lower access level' do
- project.add_guest(user)
-
- call_api
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- def jira_get(path, user_agent = 'Jira DVCS Connector/3.2.4')
- get path, headers: { 'User-Agent' => user_agent }
- end
-
- def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil, admin_mode: false)
- api(
- path,
- user,
- version: 'v3',
- personal_access_token: personal_access_token,
- oauth_access_token: oauth_access_token,
- admin_mode: admin_mode
- )
- end
-end
diff --git a/spec/requests/api/vs_code/settings/vs_code_settings_sync_spec.rb b/spec/requests/api/vs_code/settings/vs_code_settings_sync_spec.rb
index 1055a8efded..74d19f8533c 100644
--- a/spec/requests/api/vs_code/settings/vs_code_settings_sync_spec.rb
+++ b/spec/requests/api/vs_code/settings/vs_code_settings_sync_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe API::VsCode::Settings::VsCodeSettingsSync, :aggregate_failures, factory_default: :keep, feature_category: :web_ide do
+ include GrapePathHelpers::NamedRouteMatcher
+
let_it_be(:user) { create_default(:user) }
let_it_be(:user_token) { create(:personal_access_token) }
@@ -21,6 +23,14 @@ RSpec.describe API::VsCode::Settings::VsCodeSettingsSync, :aggregate_failures, f
end
end
+ shared_examples "returns 400" do
+ it 'returns 400' do
+ get api(path, personal_access_token: user_token)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
describe 'GET /vscode/settings_sync/v1/manifest' do
let(:path) { "/vscode/settings_sync/v1/manifest" }
@@ -80,6 +90,12 @@ RSpec.describe API::VsCode::Settings::VsCodeSettingsSync, :aggregate_failures, f
it_behaves_like "returns 20x when authenticated", :no_content
it_behaves_like "returns unauthorized when not authenticated"
+ context "when resource type is invalid" do
+ let(:path) { "/vscode/settings_sync/v1/resource/foo/1" }
+
+ it_behaves_like "returns 400"
+ end
+
context 'when settings with that type are not present' do
it 'returns 204 no content and no content ETag header' do
get api(path, personal_access_token: user_token)
@@ -102,6 +118,55 @@ RSpec.describe API::VsCode::Settings::VsCodeSettingsSync, :aggregate_failures, f
end
end
+ describe 'GET /vscode/settings_sync/v1/resource/:resource_name/' do
+ let(:path) { "/vscode/settings_sync/v1/resource/settings/" }
+
+ context "when resource type is invalid" do
+ let(:path) { "/vscode/settings_sync/v1/resource/foo" }
+
+ it_behaves_like "returns 400"
+ end
+
+ it_behaves_like "returns unauthorized when not authenticated"
+ it_behaves_like "returns 20x when authenticated", :ok
+
+ context 'when settings with that type are not present' do
+ it "returns empty array response" do
+ get api(path, personal_access_token: user_token)
+
+ expect(json_response.length).to eq(0)
+ end
+ end
+
+ context 'when settings with that type are present' do
+ let_it_be(:settings) { create(:vscode_setting, content: '{ "key": "value" }') }
+
+ it 'returns settings with the correct json content' do
+ get api(path, personal_access_token: user_token)
+
+ setting_type = settings[:setting_type]
+ uuid = settings[:uuid]
+
+ resource_ref = "/api/v4/vscode/settings_sync/v1/resource/#{setting_type}/#{uuid}"
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['url']).to eq(resource_ref)
+ expect(json_response.first['created']).to eq(settings.updated_at.to_i)
+ end
+ end
+
+ context 'when setting type is machine' do
+ let(:path) { "/vscode/settings_sync/v1/resource/machines/" }
+
+ it 'created field is nil' do
+ get api(path, personal_access_token: user_token)
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['created']).to be_nil
+ end
+ end
+ end
+
describe 'POST /vscode/settings_sync/v1/resource/:resource_name' do
let(:path) { "/vscode/settings_sync/v1/resource/settings" }
@@ -138,4 +203,28 @@ RSpec.describe API::VsCode::Settings::VsCodeSettingsSync, :aggregate_failures, f
expect(response).to have_gitlab_http_status(:bad_request)
end
end
+
+ describe 'DELETE /vscode/settings_sync/v1/collection' do
+ let(:path) { "/vscode/settings_sync/v1/collection" }
+
+ subject(:request) do
+ delete api(path, personal_access_token: user_token)
+ end
+
+ it 'returns unauthorized when not authenticated' do
+ delete api(path)
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ context 'when user has one or more setting resources' do
+ before do
+ create(:vscode_setting, setting_type: 'globalState')
+ create(:vscode_setting, setting_type: 'extensions')
+ end
+
+ it 'deletes all user setting resources' do
+ expect { request }.to change { User.find(user.id).vscode_settings.count }.from(2).to(0)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index 00e38a5bb7e..cd9c5637264 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -31,8 +31,8 @@ RSpec.describe API::Wikis, feature_category: :wiki do
let(:project_wiki) { create(:project_wiki, project: project, user: user) }
let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
- let(:expected_keys_with_content) { %w(content format slug title encoding) }
- let(:expected_keys_without_content) { %w(format slug title) }
+ let(:expected_keys_with_content) { %w[content format slug title encoding front_matter] }
+ let(:expected_keys_without_content) { %w[format slug title] }
let(:wiki) { project_wiki }
shared_examples_for 'wiki API 404 Project Not Found' do
@@ -354,6 +354,18 @@ RSpec.describe API::Wikis, feature_category: :wiki do
end
include_examples 'wikis API creates wiki page'
+
+ context "with front matter title" do
+ let(:payload) { { title: 'title', front_matter: { "title" => "title in front matter" }, content: 'content' } }
+
+ it "save front matter" do
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['front_matter']).to eq(payload[:front_matter])
+ expect(json_response['content']).to include(payload[:front_matter]["title"])
+ end
+ end
end
context 'when user is maintainer' do
@@ -478,6 +490,20 @@ RSpec.describe API::Wikis, feature_category: :wiki do
include_examples 'wiki API 404 Wiki Page Not Found'
end
+
+ context "with front matter title" do
+ let(:payload) do
+ { title: 'new title', front_matter: { "title" => "title in front matter" }, content: 'new content' }
+ end
+
+ it "save front matter" do
+ put(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['front_matter']).to eq(payload[:front_matter])
+ expect(json_response['content']).to include(payload[:front_matter]["title"])
+ end
+ end
end
context 'when user is maintainer' do
diff --git a/spec/requests/application_controller_spec.rb b/spec/requests/application_controller_spec.rb
deleted file mode 100644
index 52fdf6bc69e..00000000000
--- a/spec/requests/application_controller_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe ApplicationController, type: :request, feature_category: :shared do
- let_it_be(:user) { create(:user) }
-
- before do
- sign_in(user)
- end
-
- it_behaves_like 'Base action controller' do
- subject(:request) { get root_path }
- end
-end
diff --git a/spec/requests/chaos_controller_spec.rb b/spec/requests/chaos_controller_spec.rb
deleted file mode 100644
index d2ce618b041..00000000000
--- a/spec/requests/chaos_controller_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe ChaosController, type: :request, feature_category: :tooling do
- it_behaves_like 'Base action controller' do
- before do
- # Stub leak_mem so we don't actually leak memory for the base action controller tests.
- allow(Gitlab::Chaos).to receive(:leak_mem).with(100, 30.seconds)
- end
-
- subject(:request) { get leakmem_chaos_path }
- end
-end
diff --git a/spec/requests/explore/catalog_controller_spec.rb b/spec/requests/explore/catalog_controller_spec.rb
new file mode 100644
index 00000000000..50a2240e040
--- /dev/null
+++ b/spec/requests/explore/catalog_controller_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Explore::CatalogController, feature_category: :pipeline_composition do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ shared_examples 'basic get requests' do |action|
+ let(:path) do
+ if action == :index
+ explore_catalog_index_path
+ else
+ explore_catalog_path(id: 1)
+ end
+ end
+
+ context 'with FF `global_ci_catalog`' do
+ before do
+ stub_feature_flags(global_ci_catalog: true)
+ end
+
+ it 'responds with 200' do
+ get path
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'without FF `global_ci_catalog`' do
+ before do
+ stub_feature_flags(global_ci_catalog: false)
+ end
+
+ it 'responds with 404' do
+ get path
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET #show' do
+ it_behaves_like 'basic get requests', :show
+ end
+
+ describe 'GET #index' do
+ it_behaves_like 'basic get requests', :index
+ end
+end
diff --git a/spec/requests/external_redirect/external_redirect_controller_spec.rb b/spec/requests/external_redirect/external_redirect_controller_spec.rb
new file mode 100644
index 00000000000..1b4294f5c4d
--- /dev/null
+++ b/spec/requests/external_redirect/external_redirect_controller_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe "ExternalRedirect::ExternalRedirectController requests", feature_category: :navigation do
+ let_it_be(:external_url) { 'https://google.com' }
+ let_it_be(:external_url_encoded) do
+ Addressable::URI.encode_component(external_url, Addressable::URI::CharacterClasses::QUERY)
+ end
+
+ let_it_be(:internal_url) { "#{Gitlab.config.gitlab.url}/foo/bar" }
+ let_it_be(:internal_url_encoded) do
+ Addressable::URI.encode_component(internal_url, Addressable::URI::CharacterClasses::QUERY)
+ end
+
+ let_it_be(:top_nav_partial) { 'layouts/header/_default' }
+
+ context "with valid url param" do
+ before do
+ get "/-/external_redirect?url=#{external_url_encoded}"
+ end
+
+ it "returns 200 and renders URL" do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to have_link(text: 'Proceed', href: external_url)
+ end
+
+ it "does not render nav" do
+ expect(response).not_to render_template(top_nav_partial)
+ end
+ end
+
+ context "with same origin url" do
+ before do
+ get "/-/external_redirect?url=#{internal_url_encoded}"
+ end
+
+ it "redirects" do
+ expect(response).to redirect_to(internal_url)
+ end
+ end
+
+ describe "with invalid url params" do
+ where(:case_name, :params) do
+ [
+ ["when url is bad", "url=javascript:alert(1)"],
+ ["when url is empty", "url="],
+ ["when url param is missing", ""]
+ ]
+ end
+
+ with_them do
+ it "returns 404" do
+ get "/-/external_redirect?#{params}"
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/requests/health_controller_spec.rb b/spec/requests/health_controller_spec.rb
index 3ad1d8a75b4..639f6194af9 100644
--- a/spec/requests/health_controller_spec.rb
+++ b/spec/requests/health_controller_spec.rb
@@ -73,9 +73,7 @@ RSpec.describe HealthController, feature_category: :database do
end
describe 'GET /-/readiness' do
- subject(:request) { get readiness_path, params: params, headers: headers }
-
- it_behaves_like 'Base action controller'
+ subject { get '/-/readiness', params: params, headers: headers }
shared_context 'endpoint responding with readiness data' do
context 'when requesting instance-checks' do
diff --git a/spec/requests/jira_authorizations_spec.rb b/spec/requests/jira_authorizations_spec.rb
deleted file mode 100644
index 704db7fba08..00000000000
--- a/spec/requests/jira_authorizations_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Jira authorization requests', feature_category: :integrations do
- let(:user) { create :user }
- let(:application) { create :oauth_application, scopes: 'api' }
- let(:redirect_uri) { oauth_jira_dvcs_callback_url(host: "http://www.example.com") }
-
- def generate_access_grant
- create :oauth_access_grant, application: application, resource_owner_id: user.id, redirect_uri: redirect_uri
- end
-
- describe 'POST access_token' do
- let(:client_id) { application.uid }
- let(:client_secret) { application.secret }
-
- it 'returns values similar to a POST to /oauth/token' do
- post_data = {
- client_id: client_id,
- client_secret: client_secret
- }
-
- post '/oauth/token', params: post_data.merge({
- code: generate_access_grant.token,
- grant_type: 'authorization_code',
- redirect_uri: redirect_uri
- })
- oauth_response = json_response
- oauth_response_access_token, scope, token_type = oauth_response.values_at('access_token', 'scope', 'token_type')
-
- post '/login/oauth/access_token', params: post_data.merge({
- code: generate_access_grant.token
- })
- jira_response = response.body
- jira_response_access_token = Rack::Utils.parse_nested_query(jira_response)['access_token']
-
- expect(jira_response).to include("scope=#{scope}&token_type=#{token_type}")
- expect(oauth_response_access_token).not_to eql(jira_response_access_token)
- end
-
- it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
- subject do
- post '/login/oauth/access_token', params: {
- client_id: client_id,
- client_secret: client_secret,
- code: generate_access_grant.token
- }
- end
- end
-
- context 'when authorization fails' do
- before do
- post '/login/oauth/access_token', params: {
- client_id: client_id,
- client_secret: client_secret,
- code: try(:code) || generate_access_grant.token
- }
- end
-
- shared_examples 'an unauthorized request' do
- it 'returns 401' do
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
- end
-
- context 'when client_id is invalid' do
- let(:client_id) { "invalid_id" }
-
- it_behaves_like 'an unauthorized request'
- end
-
- context 'when client_secret is invalid' do
- let(:client_secret) { "invalid_secret" }
-
- it_behaves_like 'an unauthorized request'
- end
-
- context 'when code is invalid' do
- let(:code) { "invalid_code" }
-
- it 'returns bad request' do
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
- end
- end
-end
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 965bead4068..966cc2d6d4e 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe JwtController, feature_category: :system_access do
end
end
- shared_examples 'container registry authenticator' do
+ context 'authenticating against container registry' do
context 'existing service' do
subject! { get '/jwt/auth', params: parameters }
@@ -124,7 +124,7 @@ RSpec.describe JwtController, feature_category: :system_access do
end
it 'does not log a user' do
- expect(log_data.keys).not_to include(%w(username user_id))
+ expect(log_data.keys).not_to include(%w[username user_id])
end
end
@@ -177,7 +177,7 @@ RSpec.describe JwtController, feature_category: :system_access do
end
let(:service_parameters) do
- ActionController::Parameters.new({ service: service_name, scopes: %w(scope1 scope2) }).permit!
+ ActionController::Parameters.new({ service: service_name, scopes: %w[scope1 scope2] }).permit!
end
it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap)) }
@@ -185,6 +185,21 @@ RSpec.describe JwtController, feature_category: :system_access do
it_behaves_like 'user logging'
end
+ context 'when passing a space-delimited list of scopes' do
+ let(:parameters) do
+ {
+ service: service_name,
+ scope: 'scope1 scope2'
+ }
+ end
+
+ let(:service_parameters) do
+ ActionController::Parameters.new({ service: service_name, scopes: %w[scope1 scope2] }).permit!
+ end
+
+ it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap)) }
+ end
+
context 'when user has 2FA enabled' do
let(:user) { create(:user, :two_factor) }
@@ -254,40 +269,6 @@ RSpec.describe JwtController, feature_category: :system_access do
end
end
- shared_examples 'parses a space-delimited list of scopes' do |output|
- let(:user) { create(:user) }
- let(:headers) { { authorization: credentials(user.username, user.password) } }
-
- subject! { get '/jwt/auth', params: parameters, headers: headers }
-
- let(:parameters) do
- {
- service: service_name,
- scope: 'scope1 scope2'
- }
- end
-
- let(:service_parameters) do
- ActionController::Parameters.new({ service: service_name, scopes: output }).permit!
- end
-
- it { expect(service_class).to have_received(:new).with(nil, user, service_parameters.merge(auth_type: :gitlab_or_ldap)) }
- end
-
- context 'authenticating against container registry' do
- it_behaves_like 'container registry authenticator'
- it_behaves_like 'parses a space-delimited list of scopes', %w(scope1 scope2)
-
- context 'when jwt_auth_space_delimited_scopes feature flag is disabled' do
- before do
- stub_feature_flags(jwt_auth_space_delimited_scopes: false)
- end
-
- it_behaves_like 'container registry authenticator'
- it_behaves_like 'parses a space-delimited list of scopes', ['scope1 scope2']
- end
- end
-
context 'authenticating against dependency proxy' do
let_it_be(:user) { create(:user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index bc1ba3357a4..9bf77a0f6ca 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -664,8 +664,7 @@ RSpec.describe 'Git LFS API and storage', feature_category: :source_code_managem
context 'tries to push to other project' do
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
- # I'm not sure what this tests that is different from the previous test
- it_behaves_like 'LFS http 403 response'
+ it_behaves_like 'LFS http 404 response'
end
end
@@ -1185,8 +1184,7 @@ RSpec.describe 'Git LFS API and storage', feature_category: :source_code_managem
context 'tries to push to other project' do
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
- # I'm not sure what this tests that is different from the previous test
- it_behaves_like 'LFS http 403 response'
+ it_behaves_like 'LFS http 404 response'
end
end
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index 363a16f014b..a1a713308e0 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -67,7 +67,7 @@ RSpec.describe 'Git LFS File Locking API', feature_category: :source_code_manage
expect(response).to have_gitlab_http_status(:conflict)
- expect(json_response.keys).to match_array(%w(lock message documentation_url))
+ expect(json_response.keys).to match_array(%w[lock message documentation_url])
expect(json_response['message']).to match(/already locked/)
end
@@ -84,7 +84,7 @@ RSpec.describe 'Git LFS File Locking API', feature_category: :source_code_manage
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['lock'].keys).to match_array(%w(id path locked_at owner))
+ expect(json_response['lock'].keys).to match_array(%w[id path locked_at owner])
end
end
end
@@ -103,7 +103,7 @@ RSpec.describe 'Git LFS File Locking API', feature_category: :source_code_manage
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['locks'].size).to eq(2)
- expect(json_response['locks'].first.keys).to match_array(%w(id path locked_at owner))
+ expect(json_response['locks'].first.keys).to match_array(%w[id path locked_at owner])
end
end
@@ -143,7 +143,7 @@ RSpec.describe 'Git LFS File Locking API', feature_category: :source_code_manage
it 'returns the deleted lock' do
post_lfs_json url, nil, headers
- expect(json_response['lock'].keys).to match_array(%w(id path locked_at owner))
+ expect(json_response['lock'].keys).to match_array(%w[id path locked_at owner])
end
context 'when a maintainer uses force' do
diff --git a/spec/requests/metrics_controller_spec.rb b/spec/requests/metrics_controller_spec.rb
deleted file mode 100644
index ce96906e020..00000000000
--- a/spec/requests/metrics_controller_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe MetricsController, type: :request, feature_category: :metrics do
- it_behaves_like 'Base action controller' do
- subject(:request) { get metrics_path }
- end
-end
diff --git a/spec/requests/oauth/authorizations_controller_spec.rb b/spec/requests/oauth/authorizations_controller_spec.rb
index 7887bf52542..257f238d9ef 100644
--- a/spec/requests/oauth/authorizations_controller_spec.rb
+++ b/spec/requests/oauth/authorizations_controller_spec.rb
@@ -20,10 +20,6 @@ RSpec.describe Oauth::AuthorizationsController, feature_category: :system_access
end
describe 'GET #new' do
- it_behaves_like 'Base action controller' do
- subject(:request) { get oauth_authorization_path }
- end
-
context 'when application redirect URI has a custom scheme' do
context 'when CSP is disabled' do
before do
diff --git a/spec/requests/organizations/organizations_controller_spec.rb b/spec/requests/organizations/organizations_controller_spec.rb
index fdfeb367739..4bf527f49a8 100644
--- a/spec/requests/organizations/organizations_controller_spec.rb
+++ b/spec/requests/organizations/organizations_controller_spec.rb
@@ -75,6 +75,12 @@ RSpec.describe Organizations::OrganizationsController, feature_category: :cell d
it_behaves_like 'controller action that does not require authentication'
end
+ describe 'GET #users' do
+ subject(:gitlab_request) { get users_organization_path(organization) }
+
+ it_behaves_like 'controller action that does not require authentication'
+ end
+
describe 'GET #new' do
subject(:gitlab_request) { get new_organization_path }
diff --git a/spec/requests/profiles/comment_templates_controller_spec.rb b/spec/requests/profiles/comment_templates_controller_spec.rb
index cdbfbb0a346..d58fc3f19ea 100644
--- a/spec/requests/profiles/comment_templates_controller_spec.rb
+++ b/spec/requests/profiles/comment_templates_controller_spec.rb
@@ -10,26 +10,14 @@ RSpec.describe Profiles::CommentTemplatesController, feature_category: :user_pro
end
describe 'GET #index' do
- describe 'feature flag disabled' do
- before do
- stub_feature_flags(saved_replies: false)
-
- get '/-/profile/comment_templates'
- end
-
- it { expect(response).to have_gitlab_http_status(:not_found) }
+ before do
+ get '/-/profile/comment_templates'
end
- describe 'feature flag enabled' do
- before do
- get '/-/profile/comment_templates'
- end
-
- it { expect(response).to have_gitlab_http_status(:ok) }
+ it { expect(response).to have_gitlab_http_status(:ok) }
- it 'sets hide search settings ivar' do
- expect(assigns(:hide_search_settings)).to eq(true)
- end
+ it 'sets hide search settings ivar' do
+ expect(assigns(:hide_search_settings)).to eq(true)
end
end
end
diff --git a/spec/requests/projects/merge_requests_controller_spec.rb b/spec/requests/projects/merge_requests_controller_spec.rb
index 4af8f4fac7f..1033a51cd80 100644
--- a/spec/requests/projects/merge_requests_controller_spec.rb
+++ b/spec/requests/projects/merge_requests_controller_spec.rb
@@ -187,21 +187,6 @@ RSpec.describe Projects::MergeRequestsController, feature_category: :source_code
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)['count']['all']).to eq(2)
end
-
- context 'when the FF ci_fix_performance_pipelines_json_endpoint is disabled' do
- before do
- stub_feature_flags(ci_fix_performance_pipelines_json_endpoint: false)
- end
-
- it 'returns the failed builds' do
- get pipelines_project_merge_request_path(project, merge_request, format: :json)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(Gitlab::Json.parse(response.body)['pipelines'].size).to eq(1)
- expect(Gitlab::Json.parse(response.body)['pipelines'][0]['failed_builds_count']).to eq(2)
- expect(Gitlab::Json.parse(response.body)['pipelines'][0]['failed_builds'].size).to eq(2)
- end
- end
end
private
diff --git a/spec/requests/projects/ml/model_versions_controller_spec.rb b/spec/requests/projects/ml/model_versions_controller_spec.rb
new file mode 100644
index 00000000000..bd9d798c275
--- /dev/null
+++ b/spec/requests/projects/ml/model_versions_controller_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::Ml::ModelVersionsController, feature_category: :mlops do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:another_project) { create(:project) }
+ let_it_be(:user) { project.first_owner }
+ let_it_be(:model) { create(:ml_models, :with_versions, project: project) }
+ let_it_be(:version) { model.versions.first }
+
+ let(:model_registry_enabled) { true }
+
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(user, :read_model_registry, project)
+ .and_return(model_registry_enabled)
+
+ sign_in(user)
+ end
+
+ describe 'show' do
+ let(:model_id) { model.id }
+ let(:version_id) { version.id }
+ let(:request_project) { model.project }
+
+ subject(:show_request) do
+ show_model_version
+ response
+ end
+
+ before do
+ show_request
+ end
+
+ it 'renders the template' do
+ is_expected.to render_template('projects/ml/model_versions/show')
+ end
+
+ it 'fetches the correct model_version' do
+ show_request
+
+ expect(assigns(:model)).to eq(model)
+ expect(assigns(:model_version)).to eq(version)
+ end
+
+ context 'when version id does not exist' do
+ let(:version_id) { non_existing_record_id }
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'when version and model id are correct but project is not' do
+ let(:request_project) { another_project }
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'when user does not have access' do
+ let(:model_registry_enabled) { false }
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+ end
+
+ private
+
+ def show_model_version
+ get project_ml_model_version_path(request_project, model_id, version_id)
+ end
+end
diff --git a/spec/requests/projects/ml/models_controller_spec.rb b/spec/requests/projects/ml/models_controller_spec.rb
index b4402ad9a27..cda3f777a72 100644
--- a/spec/requests/projects/ml/models_controller_spec.rb
+++ b/spec/requests/projects/ml/models_controller_spec.rb
@@ -10,14 +10,19 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
let_it_be(:model3) { create(:ml_models, project: project) }
let_it_be(:model_in_different_project) { create(:ml_models) }
- let(:model_registry_enabled) { true }
+ let(:read_model_registry) { true }
+ let(:write_model_registry) { true }
+
let(:params) { {} }
before do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?)
.with(user, :read_model_registry, project)
- .and_return(model_registry_enabled)
+ .and_return(read_model_registry)
+ allow(Ability).to receive(:allowed?)
+ .with(user, :write_model_registry, project)
+ .and_return(write_model_registry)
sign_in(user)
end
@@ -33,34 +38,59 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
end
it 'fetches the models using the finder' do
- expect(::Projects::Ml::ModelFinder).to receive(:new).with(project).and_call_original
+ expect(::Projects::Ml::ModelFinder).to receive(:new).with(project, {}).and_call_original
index_request
end
- it 'fetches the correct models' do
+ it 'fetches the correct variables', :aggregate_failures do
+ stub_const("Projects::Ml::ModelsController::MAX_MODELS_PER_PAGE", 2)
+
index_request
- expect(assigns(:paginator).records).to match_array([model3, model2, model1])
+ page_models = [model3, model2]
+ all_models = [model3, model2, model1]
+
+ expect(assigns(:paginator).records).to match_array(page_models)
+ expect(assigns(:model_count)).to be all_models.count
end
it 'does not perform N+1 sql queries' do
+ list_models
+
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { list_models }
create_list(:ml_model_versions, 2, model: model1)
create_list(:ml_model_versions, 2, model: model2)
+ create_list(:ml_models, 4, project: project)
expect { list_models }.not_to exceed_all_query_limit(control_count)
end
context 'when user does not have access' do
- let(:model_registry_enabled) { false }
+ let(:read_model_registry) { false }
it 'renders 404' do
is_expected.to have_gitlab_http_status(:not_found)
end
end
+ context 'with search params' do
+ let(:params) { { name: 'some_name', order_by: 'name', sort: 'asc' } }
+
+ it 'passes down params to the finder' do
+ expect(Projects::Ml::ModelFinder).to receive(:new).and_call_original do |_exp, params|
+ expect(params.to_h).to include({
+ name: 'some_name',
+ order_by: 'name',
+ sort: 'asc'
+ })
+ end
+
+ index_request
+ end
+ end
+
describe 'pagination' do
before do
stub_const("Projects::Ml::ModelsController::MAX_MODELS_PER_PAGE", 2)
@@ -116,7 +146,40 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
end
context 'when user does not have access' do
- let(:model_registry_enabled) { false }
+ let(:read_model_registry) { false }
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+ end
+
+ describe 'destroy' do
+ let(:model_for_deletion) do
+ create(:ml_models, project: project)
+ end
+
+ let(:model_id) { model_for_deletion.id }
+
+ subject(:delete_request) do
+ delete_model
+ response
+ end
+
+ it 'deletes the model', :aggregate_failures do
+ is_expected.to have_gitlab_http_status(:found)
+
+ expect(flash[:notice]).to eq('Model removed')
+ expect(response).to redirect_to("/#{project.full_path}/-/ml/models")
+ expect { Ml::Model.find(id: model_id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ context 'when model does not exist' do
+ let(:model_id) { non_existing_record_id }
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ describe 'when user does not have write_model_registry rights' do
+ let(:write_model_registry) { false }
it { is_expected.to have_gitlab_http_status(:not_found) }
end
@@ -131,4 +194,8 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
def show_model
get project_ml_model_path(request_project, model_id)
end
+
+ def delete_model
+ delete project_ml_model_path(project, model_id)
+ end
end
diff --git a/spec/requests/projects/service_desk_controller_spec.rb b/spec/requests/projects/service_desk_controller_spec.rb
index 05e48c2c5c7..7d881d8ea62 100644
--- a/spec/requests/projects/service_desk_controller_spec.rb
+++ b/spec/requests/projects/service_desk_controller_spec.rb
@@ -88,6 +88,16 @@ RSpec.describe Projects::ServiceDeskController, feature_category: :service_desk
expect(json_response['issue_template_key']).to eq('service_desk')
end
+ it 'sets add_external_participants_from_cc' do
+ put project_service_desk_path(project, format: :json), params: { add_external_participants_from_cc: true }
+ project.reset
+
+ settings = project.service_desk_setting
+ expect(settings).to be_present
+ expect(settings.add_external_participants_from_cc).to eq(true)
+ expect(json_response['add_external_participants_from_cc']).to eq(true)
+ end
+
it 'returns an error when update of service desk settings fails' do
put project_service_desk_path(project, format: :json), params: { issue_template_key: 'invalid key' }
diff --git a/spec/requests/registrations_controller_spec.rb b/spec/requests/registrations_controller_spec.rb
index 71f2f347f0d..8b857046a4d 100644
--- a/spec/requests/registrations_controller_spec.rb
+++ b/spec/requests/registrations_controller_spec.rb
@@ -6,9 +6,7 @@ RSpec.describe RegistrationsController, type: :request, feature_category: :syste
describe 'POST #create' do
let_it_be(:user_attrs) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) }
- subject(:request) { post user_registration_path, params: { user: user_attrs } }
-
- it_behaves_like 'Base action controller'
+ subject(:create_user) { post user_registration_path, params: { user: user_attrs } }
context 'when email confirmation is required' do
before do
@@ -17,7 +15,7 @@ RSpec.describe RegistrationsController, type: :request, feature_category: :syste
end
it 'redirects to the `users_almost_there_path`', unless: Gitlab.ee? do
- request
+ create_user
expect(response).to redirect_to(users_almost_there_path(email: user_attrs[:email]))
end
diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb
index 37474aee1ee..365b20ad4aa 100644
--- a/spec/requests/search_controller_spec.rb
+++ b/spec/requests/search_controller_spec.rb
@@ -9,7 +9,6 @@ RSpec.describe SearchController, type: :request, feature_category: :global_searc
let_it_be(:projects) { create_list(:project, 5, :public, :repository, :wiki_repo) }
before do
- stub_feature_flags(super_sidebar_nav_enrolled: false)
login_as(user)
end
diff --git a/spec/requests/sessions_spec.rb b/spec/requests/sessions_spec.rb
index 1a925969c5a..3428e607305 100644
--- a/spec/requests/sessions_spec.rb
+++ b/spec/requests/sessions_spec.rb
@@ -7,10 +7,6 @@ RSpec.describe 'Sessions', feature_category: :system_access do
let(:user) { create(:user) }
- it_behaves_like 'Base action controller' do
- subject(:request) { get new_user_session_path }
- end
-
context 'for authentication', :allow_forgery_protection do
it 'logout does not require a csrf token' do
login_as(user)
diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb
index d4e7dc1542a..da111831c15 100644
--- a/spec/requests/users_controller_spec.rb
+++ b/spec/requests/users_controller_spec.rb
@@ -600,7 +600,7 @@ RSpec.describe UsersController, feature_category: :user_management do
end
end
- %i(html json).each do |format|
+ %i[html json].each do |format|
context "with format: #{format}" do
let(:format) { format }
@@ -656,7 +656,7 @@ RSpec.describe UsersController, feature_category: :user_management do
end
end
- %i(html json).each do |format|
+ %i[html json].each do |format|
context "with format: #{format}" do
let(:format) { format }