summaryrefslogtreecommitdiff
path: root/spec/requests
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests')
-rw-r--r--spec/requests/api/access_requests_spec.rb2
-rw-r--r--spec/requests/api/admin/ci/variables_spec.rb2
-rw-r--r--spec/requests/api/admin/instance_clusters_spec.rb461
-rw-r--r--spec/requests/api/admin/sidekiq_spec.rb2
-rw-r--r--spec/requests/api/api_guard/admin_mode_middleware_spec.rb2
-rw-r--r--spec/requests/api/api_spec.rb10
-rw-r--r--spec/requests/api/appearance_spec.rb2
-rw-r--r--spec/requests/api/applications_spec.rb11
-rw-r--r--spec/requests/api/avatar_spec.rb2
-rw-r--r--spec/requests/api/award_emoji_spec.rb2
-rw-r--r--spec/requests/api/badges_spec.rb2
-rw-r--r--spec/requests/api/boards_spec.rb2
-rw-r--r--spec/requests/api/branches_spec.rb116
-rw-r--r--spec/requests/api/broadcast_messages_spec.rb2
-rw-r--r--spec/requests/api/ci/pipeline_schedules_spec.rb (renamed from spec/requests/api/pipeline_schedules_spec.rb)4
-rw-r--r--spec/requests/api/ci/pipelines_spec.rb (renamed from spec/requests/api/pipelines_spec.rb)6
-rw-r--r--spec/requests/api/ci/runner_spec.rb (renamed from spec/requests/api/runner_spec.rb)287
-rw-r--r--spec/requests/api/ci/runners_spec.rb (renamed from spec/requests/api/runners_spec.rb)22
-rw-r--r--spec/requests/api/commit_statuses_spec.rb2
-rw-r--r--spec/requests/api/commits_spec.rb2
-rw-r--r--spec/requests/api/composer_packages_spec.rb302
-rw-r--r--spec/requests/api/conan_packages_spec.rb840
-rw-r--r--spec/requests/api/container_registry_event_spec.rb2
-rw-r--r--spec/requests/api/deploy_keys_spec.rb2
-rw-r--r--spec/requests/api/deploy_tokens_spec.rb2
-rw-r--r--spec/requests/api/deployments_spec.rb2
-rw-r--r--spec/requests/api/discussions_spec.rb2
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb2
-rw-r--r--spec/requests/api/environments_spec.rb2
-rw-r--r--spec/requests/api/error_tracking_spec.rb2
-rw-r--r--spec/requests/api/events_spec.rb2
-rw-r--r--spec/requests/api/features_spec.rb2
-rw-r--r--spec/requests/api/files_spec.rb24
-rw-r--r--spec/requests/api/freeze_periods_spec.rb2
-rw-r--r--spec/requests/api/go_proxy_spec.rb465
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/boards/boards_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/current_user/todos_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/current_user_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/labels_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/milestones_spec.rb119
-rw-r--r--spec/requests/api/graphql/group_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/metadata_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb2
-rw-r--r--spec/requests/api/graphql/metrics/dashboard_query_spec.rb8
-rw-r--r--spec/requests/api/graphql/multiplexed_queries_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/create_alert_issue_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb55
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/branches/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/commits/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb61
-rw-r--r--spec/requests/api/graphql/mutations/design_management/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/design_management/upload_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_locked_spec.rb55
-rw-r--r--spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/jira_import/start_spec.rb5
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_labels_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_locked_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_milestone_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb24
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb6
-rw-r--r--spec/requests/api/graphql/mutations/notes/destroy_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/snippets/create_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/snippets/destroy_spec.rb2
-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.rb48
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_done_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/todos/restore_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb62
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb34
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alerts_spec.rb11
-rw-r--r--spec/requests/api/graphql/project/base_service_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/container_expiration_policy_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/grafana_integration_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/version_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/designs/designs_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/designs/notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue/notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issue_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/jira_import_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/jira_projects_spec.rb30
-rw-r--r--spec/requests/api/graphql/project/jira_service_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/labels_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb55
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/packages_spec.rb69
-rw-r--r--spec/requests/api/graphql/project/pipeline_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/project_statistics_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/release_spec.rb422
-rw-r--r--spec/requests/api/graphql/project/releases_spec.rb284
-rw-r--r--spec/requests/api/graphql/project/repository_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/tree/tree_spec.rb2
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/query_spec.rb2
-rw-r--r--spec/requests/api/graphql/read_only_spec.rb2
-rw-r--r--spec/requests/api/graphql/tasks/task_completion_status_spec.rb2
-rw-r--r--spec/requests/api/graphql/user/group_member_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user/project_member_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user_spec.rb2
-rw-r--r--spec/requests/api/graphql/users_spec.rb2
-rw-r--r--spec/requests/api/graphql_spec.rb2
-rw-r--r--spec/requests/api/group_boards_spec.rb2
-rw-r--r--spec/requests/api/group_clusters_spec.rb50
-rw-r--r--spec/requests/api/group_container_repositories_spec.rb2
-rw-r--r--spec/requests/api/group_export_spec.rb10
-rw-r--r--spec/requests/api/group_import_spec.rb3
-rw-r--r--spec/requests/api/group_labels_spec.rb2
-rw-r--r--spec/requests/api/group_milestones_spec.rb2
-rw-r--r--spec/requests/api/group_packages_spec.rb147
-rw-r--r--spec/requests/api/group_variables_spec.rb2
-rw-r--r--spec/requests/api/groups_spec.rb57
-rw-r--r--spec/requests/api/helpers_spec.rb2
-rw-r--r--spec/requests/api/import_bitbucket_server_spec.rb218
-rw-r--r--spec/requests/api/import_github_spec.rb6
-rw-r--r--spec/requests/api/internal/base_spec.rb29
-rw-r--r--spec/requests/api/internal/pages_spec.rb2
-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.rb51
-rw-r--r--spec/requests/api/issues/post_projects_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/put_projects_issues_spec.rb2
-rw-r--r--spec/requests/api/jobs_spec.rb8
-rw-r--r--spec/requests/api/keys_spec.rb2
-rw-r--r--spec/requests/api/labels_spec.rb2
-rw-r--r--spec/requests/api/lint_spec.rb2
-rw-r--r--spec/requests/api/markdown_spec.rb2
-rw-r--r--spec/requests/api/maven_packages_spec.rb569
-rw-r--r--spec/requests/api/members_spec.rb63
-rw-r--r--spec/requests/api/merge_request_approvals_spec.rb84
-rw-r--r--spec/requests/api/merge_request_diffs_spec.rb2
-rw-r--r--spec/requests/api/merge_requests_spec.rb71
-rw-r--r--spec/requests/api/metrics/dashboard/annotations_spec.rb2
-rw-r--r--spec/requests/api/metrics/user_starred_dashboards_spec.rb2
-rw-r--r--spec/requests/api/namespaces_spec.rb2
-rw-r--r--spec/requests/api/notes_spec.rb2
-rw-r--r--spec/requests/api/notification_settings_spec.rb2
-rw-r--r--spec/requests/api/npm_packages_spec.rb550
-rw-r--r--spec/requests/api/nuget_packages_spec.rb482
-rw-r--r--spec/requests/api/oauth_tokens_spec.rb2
-rw-r--r--spec/requests/api/package_files_spec.rb81
-rw-r--r--spec/requests/api/pages/internal_access_spec.rb2
-rw-r--r--spec/requests/api/pages/pages_spec.rb2
-rw-r--r--spec/requests/api/pages/private_access_spec.rb2
-rw-r--r--spec/requests/api/pages/public_access_spec.rb2
-rw-r--r--spec/requests/api/pages_domains_spec.rb2
-rw-r--r--spec/requests/api/project_clusters_spec.rb55
-rw-r--r--spec/requests/api/project_container_repositories_spec.rb2
-rw-r--r--spec/requests/api/project_events_spec.rb2
-rw-r--r--spec/requests/api/project_export_spec.rb6
-rw-r--r--spec/requests/api/project_hooks_spec.rb2
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/requests/api/project_milestones_spec.rb2
-rw-r--r--spec/requests/api/project_packages_spec.rb272
-rw-r--r--spec/requests/api/project_repository_storage_moves_spec.rb2
-rw-r--r--spec/requests/api/project_snapshots_spec.rb2
-rw-r--r--spec/requests/api/project_snippets_spec.rb37
-rw-r--r--spec/requests/api/project_statistics_spec.rb2
-rw-r--r--spec/requests/api/project_templates_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb117
-rw-r--r--spec/requests/api/protected_branches_spec.rb2
-rw-r--r--spec/requests/api/protected_tags_spec.rb2
-rw-r--r--spec/requests/api/pypi_packages_spec.rb259
-rw-r--r--spec/requests/api/release/links_spec.rb2
-rw-r--r--spec/requests/api/releases_spec.rb2
-rw-r--r--spec/requests/api/remote_mirrors_spec.rb2
-rw-r--r--spec/requests/api/repositories_spec.rb18
-rw-r--r--spec/requests/api/resource_label_events_spec.rb2
-rw-r--r--spec/requests/api/resource_milestone_events_spec.rb2
-rw-r--r--spec/requests/api/resource_state_events_spec.rb105
-rw-r--r--spec/requests/api/search_spec.rb2
-rw-r--r--spec/requests/api/services_spec.rb2
-rw-r--r--spec/requests/api/settings_spec.rb12
-rw-r--r--spec/requests/api/sidekiq_metrics_spec.rb2
-rw-r--r--spec/requests/api/snippets_spec.rb156
-rw-r--r--spec/requests/api/statistics_spec.rb2
-rw-r--r--spec/requests/api/submodules_spec.rb2
-rw-r--r--spec/requests/api/suggestions_spec.rb2
-rw-r--r--spec/requests/api/system_hooks_spec.rb2
-rw-r--r--spec/requests/api/tags_spec.rb2
-rw-r--r--spec/requests/api/task_completion_status_spec.rb2
-rw-r--r--spec/requests/api/templates_spec.rb2
-rw-r--r--spec/requests/api/terraform/state_spec.rb63
-rw-r--r--spec/requests/api/todos_spec.rb2
-rw-r--r--spec/requests/api/triggers_spec.rb2
-rw-r--r--spec/requests/api/user_counts_spec.rb2
-rw-r--r--spec/requests/api/users_spec.rb10
-rw-r--r--spec/requests/api/variables_spec.rb151
-rw-r--r--spec/requests/api/version_spec.rb2
-rw-r--r--spec/requests/api/wikis_spec.rb306
-rw-r--r--spec/requests/boards/lists_controller_spec.rb2
-rw-r--r--spec/requests/git_http_spec.rb2
-rw-r--r--spec/requests/groups/milestones_controller_spec.rb2
-rw-r--r--spec/requests/groups/registry/repositories_controller_spec.rb2
-rw-r--r--spec/requests/health_controller_spec.rb36
-rw-r--r--spec/requests/import/gitlab_groups_controller_spec.rb4
-rw-r--r--spec/requests/import/gitlab_projects_controller_spec.rb2
-rw-r--r--spec/requests/jwt_controller_spec.rb2
-rw-r--r--spec/requests/lfs_http_spec.rb2
-rw-r--r--spec/requests/lfs_locks_api_spec.rb2
-rw-r--r--spec/requests/oauth_tokens_spec.rb2
-rw-r--r--spec/requests/openid_connect_spec.rb27
-rw-r--r--spec/requests/product_analytics/collector_app_attack_spec.rb41
-rw-r--r--spec/requests/product_analytics/collector_app_spec.rb73
-rw-r--r--spec/requests/profiles/notifications_controller_spec.rb2
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb2
-rw-r--r--spec/requests/projects/incident_management/pagerduty_incidents_spec.rb36
-rw-r--r--spec/requests/projects/merge_requests/creations_spec.rb2
-rw-r--r--spec/requests/projects/merge_requests_discussions_spec.rb2
-rw-r--r--spec/requests/projects/metrics_dashboard_spec.rb85
-rw-r--r--spec/requests/projects/uploads_spec.rb2
-rw-r--r--spec/requests/rack_attack_global_spec.rb2
-rw-r--r--spec/requests/request_profiler_spec.rb2
-rw-r--r--spec/requests/self_monitoring_project_spec.rb2
-rw-r--r--spec/requests/sessions_spec.rb2
-rw-r--r--spec/requests/user_activity_spec.rb2
-rw-r--r--spec/requests/user_avatar_spec.rb2
-rw-r--r--spec/requests/user_spoofs_ip_spec.rb2
247 files changed, 7677 insertions, 989 deletions
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb
index 52bc81cff18..223d740a004 100644
--- a/spec/requests/api/access_requests_spec.rb
+++ b/spec/requests/api/access_requests_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::AccessRequests do
+RSpec.describe API::AccessRequests do
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:access_requester) { create(:user) }
diff --git a/spec/requests/api/admin/ci/variables_spec.rb b/spec/requests/api/admin/ci/variables_spec.rb
index 185fde17e1b..812ee93ad21 100644
--- a/spec/requests/api/admin/ci/variables_spec.rb
+++ b/spec/requests/api/admin/ci/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ::API::Admin::Ci::Variables do
+RSpec.describe ::API::Admin::Ci::Variables do
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/admin/instance_clusters_spec.rb b/spec/requests/api/admin/instance_clusters_spec.rb
new file mode 100644
index 00000000000..b68541b5d92
--- /dev/null
+++ b/spec/requests/api/admin/instance_clusters_spec.rb
@@ -0,0 +1,461 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::API::Admin::InstanceClusters do
+ include KubernetesHelpers
+
+ let_it_be(:regular_user) { create(:user) }
+ let_it_be(:admin_user) { create(:admin) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:project_cluster) do
+ create(:cluster, :project, :provided_by_gcp,
+ user: admin_user,
+ projects: [project])
+ end
+ let(:project_cluster_id) { project_cluster.id }
+
+ describe "GET /admin/clusters" do
+ let_it_be(:clusters) do
+ create_list(:cluster, 3, :provided_by_gcp, :instance, :production_environment)
+ end
+
+ context "when authenticated as a non-admin user" do
+ it 'returns 403' do
+ get api('/admin/clusters', regular_user)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context "when authenticated as admin" do
+ before do
+ get api("/admin/clusters", admin_user)
+ end
+
+ it 'returns 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'includes pagination headers' do
+ expect(response).to include_pagination_headers
+ end
+
+ it 'only returns the instance clusters' do
+ cluster_ids = json_response.map { |cluster| cluster['id'] }
+ expect(cluster_ids).to match_array(clusters.pluck(:id))
+ expect(cluster_ids).not_to include(project_cluster_id)
+ end
+ end
+ end
+
+ describe "GET /admin/clusters/:cluster_id" do
+ let_it_be(:platform_kubernetes) do
+ create(:cluster_platform_kubernetes, :configured)
+ end
+
+ let_it_be(:cluster) do
+ create(:cluster, :instance, :provided_by_gcp, :with_domain,
+ platform_kubernetes: platform_kubernetes,
+ user: admin_user)
+ end
+
+ let(:cluster_id) { cluster.id }
+
+ context "when authenticated as admin" do
+ before do
+ get api("/admin/clusters/#{cluster_id}", admin_user)
+ end
+
+ context "when no cluster associated to the ID" do
+ let(:cluster_id) { 1337 }
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context "when cluster with cluster_id exists" do
+ it 'returns 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns the cluster with cluster_id' do
+ expect(json_response['id']).to eq(cluster.id)
+ end
+
+ it 'returns the cluster information' do
+ expect(json_response['provider_type']).to eq('gcp')
+ expect(json_response['platform_type']).to eq('kubernetes')
+ expect(json_response['environment_scope']).to eq('*')
+ expect(json_response['cluster_type']).to eq('instance_type')
+ expect(json_response['domain']).to eq('example.com')
+ end
+
+ it 'returns kubernetes platform information' do
+ platform = json_response['platform_kubernetes']
+
+ expect(platform['api_url']).to eq('https://kubernetes.example.com')
+ expect(platform['ca_cert']).to be_present
+ end
+
+ it 'returns user information' do
+ user = json_response['user']
+
+ expect(user['id']).to eq(admin_user.id)
+ expect(user['username']).to eq(admin_user.username)
+ end
+
+ it 'returns GCP provider information' do
+ gcp_provider = json_response['provider_gcp']
+
+ expect(gcp_provider['cluster_id']).to eq(cluster.id)
+ expect(gcp_provider['status_name']).to eq('created')
+ expect(gcp_provider['gcp_project_id']).to eq('test-gcp-project')
+ expect(gcp_provider['zone']).to eq('us-central1-a')
+ expect(gcp_provider['machine_type']).to eq('n1-standard-2')
+ expect(gcp_provider['num_nodes']).to eq(3)
+ expect(gcp_provider['endpoint']).to eq('111.111.111.111')
+ end
+
+ context 'when cluster has no provider' do
+ let(:cluster) do
+ create(:cluster, :instance, :provided_by_user, :production_environment)
+ end
+
+ it 'does not include GCP provider info' do
+ expect(json_response['provider_gcp']).not_to be_present
+ end
+ end
+
+ context 'when trying to get a project cluster via the instance cluster endpoint' do
+ it 'returns 404' do
+ get api("/admin/clusters/#{project_cluster_id}", admin_user)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context "when authenticated as a non-admin user" do
+ it 'returns 403' do
+ get api("/admin/clusters/#{cluster_id}", regular_user)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+ end
+
+ describe "POST /admin/clusters/add" do
+ let(:api_url) { 'https://example.com' }
+ let(:authorization_type) { 'rbac' }
+ let(:clusterable) { Clusters::Instance.new }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ authorization_type: authorization_type
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-instance-cluster',
+ domain: 'domain.example.com',
+ managed: false,
+ platform_kubernetes_attributes: platform_kubernetes_attributes,
+ clusterable: clusterable
+ }
+ end
+
+ let(:multiple_cluster_params) do
+ {
+ name: 'multiple-instance-cluster',
+ environment_scope: 'staging/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ let(:invalid_cluster_params) do
+ {
+ environment_scope: 'production/*',
+ domain: 'domain.example.com',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'authorized user' do
+ before do
+ post api('/admin/clusters/add', admin_user), params: cluster_params
+ end
+
+ context 'with valid params' do
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'creates a new Clusters::Cluster', :aggregate_failures do
+ cluster_result = Clusters::Cluster.find(json_response["id"])
+ platform_kubernetes = cluster_result.platform
+ expect(cluster_result).to be_user
+ expect(cluster_result).to be_kubernetes
+ expect(cluster_result.clusterable).to be_a Clusters::Instance
+ expect(cluster_result.cluster_type).to eq('instance_type')
+ expect(cluster_result.name).to eq('test-instance-cluster')
+ expect(cluster_result.domain).to eq('domain.example.com')
+ expect(cluster_result.environment_scope).to eq('*')
+ expect(cluster_result.enabled).to eq(true)
+ expect(platform_kubernetes.authorization_type).to eq('rbac')
+ expect(cluster_result.managed).to be_falsy
+ expect(platform_kubernetes.api_url).to eq("https://example.com")
+ expect(platform_kubernetes.token).to eq('sample-token')
+ end
+
+ context 'when user does not indicate authorization type' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token'
+ }
+ end
+
+ it 'defaults to RBAC' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform_kubernetes.rbac?).to be_truthy
+ end
+ end
+
+ context 'when user sets authorization type as ABAC' do
+ let(:authorization_type) { 'abac' }
+
+ it 'creates an ABAC cluster' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform.abac?).to be_truthy
+ end
+ end
+
+ context 'when an instance cluster already exists' do
+ it 'allows user to add multiple clusters' do
+ post api('/admin/clusters/add', admin_user), params: multiple_cluster_params
+
+ expect(Clusters::Instance.new.clusters.count).to eq(2)
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ context 'when missing a required parameter' do
+ it 'responds with 400' do
+ post api('/admin/clusters/add', admin_user), params: invalid_cluster_params
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eql('name is missing')
+ end
+ end
+
+ context 'with a malformed api url' do
+ let(:api_url) { 'invalid_api_url' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['platform_kubernetes.api_url'].first).to be_present
+ end
+ end
+ end
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ post api('/admin/clusters/add', regular_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe 'PUT /admin/clusters/:cluster_id' do
+ let(:api_url) { 'https://example.com' }
+
+ let(:update_params) do
+ {
+ domain: domain,
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ let(:domain) { 'new-domain.com' }
+ let(:platform_kubernetes_attributes) { {} }
+
+ let_it_be(:cluster) do
+ create(:cluster, :instance, :provided_by_gcp, domain: 'old-domain.com')
+ end
+
+ context 'authorized user' do
+ before do
+ put api("/admin/clusters/#{cluster.id}", admin_user), params: update_params
+
+ cluster.reload
+ end
+
+ context 'with valid params' do
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'updates cluster attributes' do
+ expect(cluster.domain).to eq('new-domain.com')
+ end
+ end
+
+ context 'with invalid params' do
+ let(:domain) { 'invalid domain' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'does not update cluster attributes' do
+ expect(cluster.domain).to eq('old-domain.com')
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['domain'].first).to match('contains invalid characters (valid characters: [a-z0-9\\-])')
+ end
+ end
+
+ context 'with a GCP cluster' do
+ context 'when user tries to change GCP specific fields' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: 'https://new-api-url.com',
+ token: 'new-sample-token'
+ }
+ end
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns validation error' do
+ expect(json_response['message']['platform_kubernetes.base'].first).to eq(_('Cannot modify managed Kubernetes cluster'))
+ end
+ end
+
+ context 'when user tries to change domain' do
+ let(:domain) { 'new-domain.com' }
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with an user cluster' do
+ let(:api_url) { 'https://new-api-url.com' }
+
+ let(:cluster) do
+ create(:cluster, :instance, :provided_by_user, :production_environment)
+ end
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'new-sample-token'
+ }
+ end
+
+ let(:update_params) do
+ {
+ name: 'new-name',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'updates platform kubernetes attributes' do
+ platform_kubernetes = cluster.platform_kubernetes
+
+ expect(cluster.name).to eq('new-name')
+ expect(platform_kubernetes.api_url).to eq('https://new-api-url.com')
+ expect(platform_kubernetes.token).to eq('new-sample-token')
+ end
+ end
+
+ context 'with a cluster that does not exist' do
+ let(:cluster_id) { 1337 }
+
+ it 'returns 404' do
+ put api("/admin/clusters/#{cluster_id}", admin_user), params: update_params
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when trying to update a project cluster via the instance cluster endpoint' do
+ it 'returns 404' do
+ put api("/admin/clusters/#{project_cluster_id}", admin_user), params: update_params
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ put api("/admin/clusters/#{cluster.id}", regular_user), params: update_params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe 'DELETE /admin/clusters/:cluster_id' do
+ let(:cluster_params) { { cluster_id: cluster.id } }
+
+ let_it_be(:cluster) do
+ create(:cluster, :instance, :provided_by_gcp)
+ end
+
+ context 'authorized user' do
+ before do
+ delete api("/admin/clusters/#{cluster.id}", admin_user), params: cluster_params
+ end
+
+ it 'responds with 204' do
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'deletes the cluster' do
+ expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy
+ end
+
+ context 'with a cluster that does not exist' do
+ let(:cluster_id) { 1337 }
+
+ it 'returns 404' do
+ delete api("/admin/clusters/#{cluster_id}", admin_user)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when trying to update a project cluster via the instance cluster endpoint' do
+ it 'returns 404' do
+ delete api("/admin/clusters/#{project_cluster_id}", admin_user)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ delete api("/admin/clusters/#{cluster.id}", regular_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/admin/sidekiq_spec.rb b/spec/requests/api/admin/sidekiq_spec.rb
index 303b62f4436..3c488816bed 100644
--- a/spec/requests/api/admin/sidekiq_spec.rb
+++ b/spec/requests/api/admin/sidekiq_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Admin::Sidekiq, :clean_gitlab_redis_queues do
+RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues do
let_it_be(:admin) { create(:admin) }
describe 'DELETE /admin/sidekiq/queues/:queue_name' do
diff --git a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
index 7175076e56d..4b477f829a7 100644
--- a/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
+++ b/spec/requests/api/api_guard/admin_mode_middleware_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::APIGuard::AdminModeMiddleware, :do_not_mock_admin_mode, :request_store do
+RSpec.describe API::APIGuard::AdminModeMiddleware, :do_not_mock_admin_mode, :request_store do
let(:user) { create(:admin) }
it 'is loaded' do
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index 201c0d1796c..bd0426601db 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::API do
+RSpec.describe API::API do
include GroupAPIHelpers
describe 'Record user last activity in after hook' do
@@ -36,6 +36,14 @@ describe API::API do
expect(response).to have_gitlab_http_status(:ok)
end
+ it 'does not authorize user for revoked token' do
+ revoked = create(:personal_access_token, :revoked, user: user, scopes: [:read_api])
+
+ get api('/groups', personal_access_token: revoked)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
it 'does not authorize user for post request' do
params = attributes_for_group_api
diff --git a/spec/requests/api/appearance_spec.rb b/spec/requests/api/appearance_spec.rb
index f8c3db70d16..69176e18d2e 100644
--- a/spec/requests/api/appearance_spec.rb
+++ b/spec/requests/api/appearance_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Appearance, 'Appearance' do
+RSpec.describe API::Appearance, 'Appearance' do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb
index cd341ad134e..63fbf6e32dd 100644
--- a/spec/requests/api/applications_spec.rb
+++ b/spec/requests/api/applications_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Applications, :api do
+RSpec.describe API::Applications, :api do
let(:admin_user) { create(:user, admin: true) }
let(:user) { create(:user, admin: false) }
let!(:application) { create(:application, name: 'another_application', redirect_uri: 'http://other_application.url', scopes: '') }
@@ -74,14 +74,15 @@ describe API::Applications, :api do
expect(json_response['error']).to eq('scopes is missing')
end
- it 'does not allow creating an application with confidential set to nil' do
+ it 'defaults to creating an application with confidential' do
expect do
post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: '', confidential: nil }
- end.not_to change { Doorkeeper::Application.count }
+ end.to change { Doorkeeper::Application.count }.by(1)
- expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response).to have_gitlab_http_status(:created)
expect(json_response).to be_a Hash
- expect(json_response['message']['confidential'].first).to eq('is not included in the list')
+ expect(json_response['callback_url']).to eq('http://application.url')
+ expect(json_response['confidential']).to be true
end
end
diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb
index 45e34b7894b..656a086e550 100644
--- a/spec/requests/api/avatar_spec.rb
+++ b/spec/requests/api/avatar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Avatar do
+RSpec.describe API::Avatar do
let(:gravatar_service) { double('GravatarService') }
describe 'GET /avatar' do
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 543fe970abd..1c825949ae8 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::AwardEmoji do
+RSpec.describe API::AwardEmoji do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:issue) { create(:issue, project: project) }
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
index d7f9b7d010b..99d224cb8e9 100644
--- a/spec/requests/api/badges_spec.rb
+++ b/spec/requests/api/badges_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Badges do
+RSpec.describe API::Badges do
let(:maintainer) { create(:user, username: 'maintainer_user') }
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index d761b371821..f0d3afd0af7 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Boards do
+RSpec.describe API::Boards do
let_it_be(:user) { create(:user) }
let_it_be(:non_member) { create(:user) }
let_it_be(:guest) { create(:user) }
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index f2dc5b1c045..46acd92803f 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Branches do
+RSpec.describe API::Branches do
let_it_be(:user) { create(:user) }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
@@ -17,6 +17,7 @@ describe API::Branches do
before do
project.add_maintainer(user)
project.repository.add_branch(user, 'ends-with.txt', branch_sha)
+ stub_feature_flags(branch_list_keyset_pagination: false)
end
describe "GET /projects/:id/repository/branches" do
@@ -29,16 +30,6 @@ describe API::Branches do
end
end
- it 'returns the repository branches' do
- get api(route, current_user), params: { per_page: 100 }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/branches')
- expect(response).to include_pagination_headers
- branch_names = json_response.map { |x| x['name'] }
- expect(branch_names).to match_array(project.repository.branch_names)
- end
-
def check_merge_status(json_response)
merged, unmerged = json_response.partition { |branch| branch['merged'] }
merged_branches = merged.map { |branch| branch['name'] }
@@ -47,22 +38,107 @@ describe API::Branches do
expect(project.repository.merged_branch_names(unmerged_branches)).to be_empty
end
- it 'determines only a limited number of merged branch names' do
- expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
+ context 'with branch_list_keyset_pagination feature off' do
+ context 'with legacy pagination params' do
+ it 'returns the repository branches' do
+ get api(route, current_user), params: { per_page: 100 }
- get api(route, current_user), params: { per_page: 2 }
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/branches')
+ expect(response).to include_pagination_headers
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
+ end
- expect(response).to have_gitlab_http_status(:ok)
+ it 'determines only a limited number of merged branch names' do
+ expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
+
+ get api(route, current_user), params: { per_page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 2
+
+ check_merge_status(json_response)
+ end
- check_merge_status(json_response)
+ it 'merge status matches reality on paginated input' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name')[20].name
+
+ get api(route, current_user), params: { per_page: 20, page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 20
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
+
+ context 'with gitaly pagination params ' do
+ it 'merge status matches reality on paginated input' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
+
+ get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 20
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
end
- it 'merge status matches reality on paginated input' do
- get api(route, current_user), params: { per_page: 20, page: 2 }
+ context 'with branch_list_keyset_pagination feature on' do
+ before do
+ stub_feature_flags(branch_list_keyset_pagination: true)
+ end
- expect(response).to have_gitlab_http_status(:ok)
+ context 'with gitaly pagination params ' do
+ it 'returns the repository branches' do
+ get api(route, current_user), params: { per_page: 100 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/branches')
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
+ end
+
+ it 'determines only a limited number of merged branch names' do
+ expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
+
+ get api(route, current_user), params: { per_page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 2
+
+ check_merge_status(json_response)
+ end
- check_merge_status(json_response)
+ it 'merge status matches reality on paginated input' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name').drop_while { |b| b.name <= 'feature' }.first.name
+
+ get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 20
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
+
+ context 'with legacy pagination params' do
+ it 'ignores legacy pagination params' do
+ expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
+ get api(route, current_user), params: { per_page: 20, page: 2 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.first['name']).to eq(expected_first_branch_name)
+
+ check_merge_status(json_response)
+ end
+ end
end
context 'when repository is disabled' do
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index 9bfbbe0daab..b5b6ce106e5 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::BroadcastMessages do
+RSpec.describe API::BroadcastMessages do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
let_it_be(:message) { create(:broadcast_message) }
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/ci/pipeline_schedules_spec.rb
index 98eaf36b14e..e0199b7b51c 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/ci/pipeline_schedules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::PipelineSchedules do
+RSpec.describe API::Ci::PipelineSchedules do
let_it_be(:developer) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, public_builds: false) }
@@ -24,7 +24,7 @@ describe API::PipelineSchedules do
.each do |pipeline_schedule|
create(:user).tap do |user|
project.add_developer(user)
- pipeline_schedule.update(owner: user)
+ pipeline_schedule.update!(owner: user)
end
pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
end
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb
index f57223f1de5..c9ca806e2c4 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/ci/pipelines_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Pipelines do
+RSpec.describe API::Ci::Pipelines do
let_it_be(:user) { create(:user) }
let_it_be(:non_member) { create(:user) }
@@ -106,11 +106,11 @@ describe API::Pipelines do
end
end
- HasStatus::AVAILABLE_STATUSES.each do |target|
+ Ci::HasStatus::AVAILABLE_STATUSES.each do |target|
context "when status is #{target}" do
before do
create(:ci_pipeline, project: project, status: target)
- exception_status = HasStatus::AVAILABLE_STATUSES - [target]
+ exception_status = Ci::HasStatus::AVAILABLE_STATUSES - [target]
create(:ci_pipeline, project: project, status: exception_status.sample)
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/ci/runner_spec.rb
index 774615757b9..c8718309bf2 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/ci/runner_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Runner, :clean_gitlab_redis_shared_state do
+RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
include StubGitlabCalls
include RedisHelpers
include WorkhorseHelpers
@@ -13,7 +13,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
stub_feature_flags(ci_enable_live_trace: true)
stub_gitlab_calls
stub_application_setting(runners_registration_token: registration_token)
- allow_any_instance_of(Ci::Runner).to receive(:cache_attributes)
+ allow_any_instance_of(::Ci::Runner).to receive(:cache_attributes)
end
describe '/api/v4/runners' do
@@ -38,7 +38,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it 'creates runner with default values' do
post api('/runners'), params: { token: registration_token }
- runner = Ci::Runner.first
+ runner = ::Ci::Runner.first
expect(response).to have_gitlab_http_status(:created)
expect(json_response['id']).to eq(runner.id)
@@ -57,7 +57,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(project.runners.size).to eq(1)
- runner = Ci::Runner.first
+ runner = ::Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(project.runners_token)
expect(runner).to be_project_type
@@ -72,7 +72,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(group.runners.reload.size).to eq(1)
- runner = Ci::Runner.first
+ runner = ::Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(group.runners_token)
expect(runner).to be_group_type
@@ -88,7 +88,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.description).to eq('server.hostname')
+ expect(::Ci::Runner.first.description).to eq('server.hostname')
end
end
@@ -100,7 +100,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
+ expect(::Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
end
end
@@ -114,8 +114,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.run_untagged).to be false
- expect(Ci::Runner.first.tag_list.sort).to eq(['tag'])
+ expect(::Ci::Runner.first.run_untagged).to be false
+ expect(::Ci::Runner.first.tag_list.sort).to eq(['tag'])
end
end
@@ -141,7 +141,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.locked).to be true
+ expect(::Ci::Runner.first.locked).to be true
end
end
@@ -154,7 +154,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.active).to be true
+ expect(::Ci::Runner.first.active).to be true
end
end
@@ -166,7 +166,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.active).to be false
+ expect(::Ci::Runner.first.active).to be false
end
end
end
@@ -180,7 +180,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.ref_protected?).to be true
+ expect(::Ci::Runner.first.ref_protected?).to be true
end
end
@@ -192,7 +192,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.ref_protected?).to be false
+ expect(::Ci::Runner.first.ref_protected?).to be false
end
end
end
@@ -205,7 +205,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.maximum_timeout).to eq(9000)
+ expect(::Ci::Runner.first.maximum_timeout).to eq(9000)
end
context 'when maximum job timeout is empty' do
@@ -216,7 +216,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.maximum_timeout).to be_nil
+ expect(::Ci::Runner.first.maximum_timeout).to be_nil
end
end
end
@@ -232,7 +232,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
}
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
+ expect(::Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
end
end
end
@@ -243,7 +243,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
headers: { 'X-Forwarded-For' => '123.111.123.111' }
expect(response).to have_gitlab_http_status(:created)
- expect(Ci::Runner.first.ip_address).to eq('123.111.123.111')
+ expect(::Ci::Runner.first.ip_address).to eq('123.111.123.111')
end
end
@@ -271,7 +271,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
delete api('/runners'), params: { token: runner.token }
expect(response).to have_gitlab_http_status(:no_content)
- expect(Ci::Runner.count).to eq(0)
+ expect(::Ci::Runner.count).to eq(0)
end
it_behaves_like '412 response' do
@@ -518,6 +518,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
request_job info: { platform: :darwin }
expect(response).to have_gitlab_http_status(:created)
+ expect(response.headers['Content-Type']).to eq('application/json')
expect(response.headers).not_to have_key('X-GitLab-Last-Update')
expect(runner.reload.platform).to eq('darwin')
expect(json_response['id']).to eq(job.id)
@@ -537,7 +538,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
it 'creates persistent ref' do
- expect_any_instance_of(Ci::PersistentRef).to receive(:create_ref)
+ expect_any_instance_of(::Ci::PersistentRef).to receive(:create_ref)
.with(job.sha, "refs/#{Repository::REF_PIPELINES}/#{job.commit_id}")
request_job info: { platform: :darwin }
@@ -569,6 +570,24 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'when a Gitaly exception is thrown during response' do
+ before do
+ allow_next_instance_of(Ci::BuildRunnerPresenter) do |instance|
+ allow(instance).to receive(:artifacts).and_raise(GRPC::DeadlineExceeded)
+ end
+ end
+
+ it 'fails the job as a scheduler failure' do
+ request_job
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(job.reload.failed?).to be_truthy
+ expect(job.failure_reason).to eq('scheduler_failure')
+ expect(job.runner_id).to eq(runner.id)
+ expect(job.runner_session).to be_nil
+ end
+ end
+
context 'when GIT_DEPTH is not specified and there is no default git depth for the project' do
before do
project.update!(ci_default_git_depth: nil)
@@ -651,9 +670,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when job is for a release' do
let!(:job) { create(:ci_build, :release_options, pipeline: pipeline) }
- context 'when `release_steps` is passed by the runner' do
+ context 'when `multi_build_steps` is passed by the runner' do
it 'exposes release info' do
- request_job info: { features: { release_steps: true } }
+ request_job info: { features: { multi_build_steps: true } }
expect(response).to have_gitlab_http_status(:created)
expect(response.headers).not_to have_key('X-GitLab-Last-Update')
@@ -668,7 +687,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
{
"name" => "release",
"script" =>
- "release-cli create --ref \"$CI_COMMIT_SHA\" --name \"Release $CI_COMMIT_SHA\" --tag-name \"release-$CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\"",
+ ["release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\""],
"timeout" => 3600,
"when" => "on_success",
"allow_failure" => false
@@ -677,7 +696,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when `release_steps` is not passed by the runner' do
+ context 'when `multi_build_steps` is not passed by the runner' do
it 'drops the job' do
request_job
@@ -749,7 +768,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when concurrently updating a job' do
before do
- expect_any_instance_of(Ci::Build).to receive(:run!)
+ expect_any_instance_of(::Ci::Build).to receive(:run!)
.and_raise(ActiveRecord::StaleObjectError.new(nil, nil))
end
@@ -890,7 +909,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let!(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, builds: [job], trigger: trigger) }
before do
- project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+ project.variables << ::Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
shared_examples 'expected variables behavior' do
@@ -1090,7 +1109,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
def request_job(token = runner.token, **params)
new_params = params.merge(token: token, last_update: last_update)
- post api('/jobs/request'), params: new_params, headers: { 'User-Agent' => user_agent }
+ post api('/jobs/request'), params: new_params.to_json, headers: { 'User-Agent' => user_agent, 'Content-Type': 'application/json' }
end
end
@@ -1099,7 +1118,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let_it_be(:project) { create(:project, :repository) }
let(:runner) { create(:ci_runner, :project, projects: [project]) }
- let(:service) { Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
+ let(:service) { ::Ci::CreateWebIdeTerminalService.new(project, user, ref: 'master').execute }
let(:pipeline) { service[:pipeline] }
let(:build) { pipeline.builds.first }
let(:job) { {} }
@@ -1592,8 +1611,105 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
job.run!
end
+ shared_examples_for 'rejecting artifacts that are too large' do
+ let(:filesize) { 100.megabytes.to_i }
+ let(:sample_max_size) { (filesize / 1.megabyte) - 10 } # Set max size to be smaller than file size to trigger error
+
+ shared_examples_for 'failed request' do
+ it 'responds with payload too large error' do
+ send_request
+
+ expect(response).to have_gitlab_http_status(:payload_too_large)
+ end
+ end
+
+ context 'based on plan limit setting' do
+ let(:application_max_size) { sample_max_size + 100 }
+ let(:limit_name) { "#{Ci::JobArtifact::PLAN_LIMIT_PREFIX}archive" }
+
+ before do
+ create(:plan_limits, :default_plan, limit_name => sample_max_size)
+ stub_application_setting(max_artifacts_size: application_max_size)
+ end
+
+ context 'and feature flag ci_max_artifact_size_per_type is enabled' do
+ before do
+ stub_feature_flags(ci_max_artifact_size_per_type: true)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'and feature flag ci_max_artifact_size_per_type is disabled' do
+ before do
+ stub_feature_flags(ci_max_artifact_size_per_type: false)
+ end
+
+ it 'bases of project closest setting' do
+ send_request
+
+ expect(response).to have_gitlab_http_status(success_code)
+ end
+ end
+ end
+
+ context 'based on application setting' do
+ before do
+ stub_application_setting(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'based on root namespace setting' do
+ let(:application_max_size) { sample_max_size + 10 }
+
+ before do
+ stub_application_setting(max_artifacts_size: application_max_size)
+ root_namespace.update!(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'based on child namespace setting' do
+ let(:application_max_size) { sample_max_size + 10 }
+ let(:root_namespace_max_size) { sample_max_size + 10 }
+
+ before do
+ stub_application_setting(max_artifacts_size: application_max_size)
+ root_namespace.update!(max_artifacts_size: root_namespace_max_size)
+ namespace.update!(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+
+ context 'based on project setting' do
+ let(:application_max_size) { sample_max_size + 10 }
+ let(:root_namespace_max_size) { sample_max_size + 10 }
+ let(:child_namespace_max_size) { sample_max_size + 10 }
+
+ before do
+ stub_application_setting(max_artifacts_size: application_max_size)
+ root_namespace.update!(max_artifacts_size: root_namespace_max_size)
+ namespace.update!(max_artifacts_size: child_namespace_max_size)
+ project.update!(max_artifacts_size: sample_max_size)
+ end
+
+ it_behaves_like 'failed request'
+ end
+ end
+
describe 'POST /api/v4/jobs/:id/artifacts/authorize' do
context 'when using token as parameter' do
+ context 'and the artifact is too large' do
+ it_behaves_like 'rejecting artifacts that are too large' do
+ let(:success_code) { :ok }
+ let(:send_request) { authorize_artifacts_with_token_in_params(filesize: filesize) }
+ end
+ end
+
context 'posting artifacts to running job' do
subject do
authorize_artifacts_with_token_in_params
@@ -1651,56 +1767,6 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
end
-
- context 'when artifact is too large' do
- let(:sample_max_size) { 100 }
-
- shared_examples_for 'rejecting too large artifacts' do
- it 'fails to post' do
- authorize_artifacts_with_token_in_params(filesize: sample_max_size.megabytes.to_i)
-
- expect(response).to have_gitlab_http_status(:payload_too_large)
- end
- end
-
- context 'based on application setting' do
- before do
- stub_application_setting(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
-
- context 'based on root namespace setting' do
- before do
- stub_application_setting(max_artifacts_size: 200)
- root_namespace.update!(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
-
- context 'based on child namespace setting' do
- before do
- stub_application_setting(max_artifacts_size: 200)
- root_namespace.update!(max_artifacts_size: 200)
- namespace.update!(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
-
- context 'based on project setting' do
- before do
- stub_application_setting(max_artifacts_size: 200)
- root_namespace.update!(max_artifacts_size: 200)
- namespace.update!(max_artifacts_size: 200)
- project.update!(max_artifacts_size: sample_max_size)
- end
-
- it_behaves_like 'rejecting too large artifacts'
- end
- end
end
context 'when using token as header' do
@@ -1757,19 +1823,36 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(json_response['ProcessLsif']).to be_truthy
end
- it 'fails to authorize too large artifact' do
- authorize_artifacts_with_token_in_headers(artifact_type: :lsif, filesize: 30.megabytes)
+ it 'adds ProcessLsifReferences header' do
+ authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
- expect(response).to have_gitlab_http_status(:payload_too_large)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['ProcessLsifReferences']).to be_truthy
end
context 'code_navigation feature flag is disabled' do
- it 'does not add ProcessLsif header' do
+ it 'responds with a forbidden error' do
stub_feature_flags(code_navigation: false)
+ authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['ProcessLsif']).to be_falsy
+ expect(json_response['ProcessLsifReferences']).to be_falsy
+ end
+ end
+ end
+ context 'code_navigation_references feature flag is disabled' do
+ it 'sets ProcessLsifReferences header to false' do
+ stub_feature_flags(code_navigation_references: false)
authorize_artifacts_with_token_in_headers(artifact_type: :lsif)
- expect(response).to have_gitlab_http_status(:forbidden)
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['ProcessLsif']).to be_truthy
+ expect(json_response['ProcessLsifReferences']).to be_falsy
+ end
end
end
end
@@ -1799,6 +1882,32 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect { upload_artifacts(file_upload, headers_with_token) }.to change { runner.reload.contacted_at }
end
+ context 'when the artifact is too large' do
+ it_behaves_like 'rejecting artifacts that are too large' do
+ # This filesize validation also happens in non remote stored files,
+ # it's just that it's hard to stub the filesize in other cases to be
+ # more than a megabyte.
+ let!(:fog_connection) do
+ stub_artifacts_object_storage(direct_upload: true)
+ end
+ let(:object) do
+ fog_connection.directories.new(key: 'artifacts').files.create(
+ key: 'tmp/uploads/12312300',
+ body: 'content'
+ )
+ end
+ let(:file_upload) { fog_to_uploaded_file(object) }
+ let(:send_request) do
+ upload_artifacts(file_upload, headers_with_token, 'file.remote_id' => '12312300')
+ end
+ let(:success_code) { :created }
+
+ before do
+ allow(object).to receive(:content_length).and_return(filesize)
+ end
+ end
+ end
+
context 'when artifacts are being stored inside of tmp path' do
before do
# by configuring this path we allow to pass temp file from any path
@@ -1877,16 +1986,6 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when artifacts file is too large' do
- it 'fails to post too large artifact' do
- stub_application_setting(max_artifacts_size: 0)
-
- upload_artifacts(file_upload, headers_with_token)
-
- expect(response).to have_gitlab_http_status(:payload_too_large)
- end
- end
-
context 'when artifacts post request does not contain file' do
it 'fails to post artifacts without file' do
post api("/jobs/#{job.id}/artifacts"), params: {}, headers: headers_with_token
@@ -2258,7 +2357,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
FileUtils.remove_entry(new_tmpdir)
end
- it' "fails to post artifacts for outside of tmp path"' do
+ it 'fails to post artifacts for outside of tmp path' do
upload_artifacts(file_upload, headers_with_token)
expect(response).to have_gitlab_http_status(:bad_request)
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index 67c258260bf..670456e5dba 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Runners do
+RSpec.describe API::Ci::Runners do
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -266,7 +266,7 @@ describe API::Runners do
delete api("/runners/#{unused_project_runner.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.project_type.count }.by(-1)
+ end.to change { ::Ci::Runner.project_type.count }.by(-1)
end
end
@@ -284,7 +284,7 @@ describe API::Runners do
end
end
- it 'returns 404 if runner does not exists' do
+ it 'returns 404 if runner does not exist' do
get api('/runners/0', admin)
expect(response).to have_gitlab_http_status(:not_found)
@@ -437,7 +437,7 @@ describe API::Runners do
end
end
- it 'returns 404 if runner does not exists' do
+ it 'returns 404 if runner does not exist' do
update_runner(0, admin, description: 'test')
expect(response).to have_gitlab_http_status(:not_found)
@@ -493,7 +493,7 @@ describe API::Runners do
delete api("/runners/#{shared_runner.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.instance_type.count }.by(-1)
+ end.to change { ::Ci::Runner.instance_type.count }.by(-1)
end
it_behaves_like '412 response' do
@@ -507,11 +507,11 @@ describe API::Runners do
delete api("/runners/#{project_runner.id}", admin)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.project_type.count }.by(-1)
+ end.to change { ::Ci::Runner.project_type.count }.by(-1)
end
end
- it 'returns 404 if runner does not exists' do
+ it 'returns 404 if runner does not exist' do
delete api('/runners/0', admin)
expect(response).to have_gitlab_http_status(:not_found)
@@ -542,7 +542,7 @@ describe API::Runners do
delete api("/runners/#{project_runner.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.project_type.count }.by(-1)
+ end.to change { ::Ci::Runner.project_type.count }.by(-1)
end
it 'does not delete group runner with guest access' do
@@ -574,7 +574,7 @@ describe API::Runners do
delete api("/runners/#{group_runner_a.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.group_type.count }.by(-1)
+ end.to change { ::Ci::Runner.group_type.count }.by(-1)
end
it 'deletes inherited group runner with owner access' do
@@ -582,7 +582,7 @@ describe API::Runners do
delete api("/runners/#{group_runner_b.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change { Ci::Runner.group_type.count }.by(-1)
+ end.to change { ::Ci::Runner.group_type.count }.by(-1)
end
it_behaves_like '412 response' do
@@ -968,7 +968,7 @@ describe API::Runners do
end
it 'does not enable locked runner' do
- project_runner2.update(locked: true)
+ project_runner2.update!(locked: true)
expect do
post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 0c0bf8b4df0..bec15b788c3 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::CommitStatuses do
+RSpec.describe API::CommitStatuses do
let!(:project) { create(:project, :repository) }
let(:commit) { project.repository.commit }
let(:guest) { create_user(:guest) }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index a423c92e2fb..724e3177173 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'mime/types'
-describe API::Commits do
+RSpec.describe API::Commits do
include ProjectForksHelper
let(:user) { create(:user) }
diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb
new file mode 100644
index 00000000000..d756a7700f6
--- /dev/null
+++ b/spec/requests/api/composer_packages_spec.rb
@@ -0,0 +1,302 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::ComposerPackages do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group, reload: true) { create(:group, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:package_name) { 'package-name' }
+ let_it_be(:project, reload: true) { create(:project, :custom_repo, files: { 'composer.json' => { name: package_name }.to_json }, group: group) }
+ let(:headers) { {} }
+
+ describe 'GET /api/v4/group/:id/-/packages/composer/packages' do
+ let(:url) { "/group/#{group.id}/-/packages/composer/packages.json" }
+
+ subject { get api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ let!(:package) { create(:composer_package, :with_metadatum, project: project) }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer package index' | :success
+ 'PUBLIC' | :guest | true | true | 'Composer package index' | :success
+ 'PUBLIC' | :developer | true | false | 'Composer package index' | :success
+ 'PUBLIC' | :guest | true | false | 'Composer package index' | :success
+ 'PUBLIC' | :developer | false | true | 'Composer package index' | :success
+ 'PUBLIC' | :guest | false | true | 'Composer package index' | :success
+ 'PUBLIC' | :developer | false | false | 'Composer package index' | :success
+ 'PUBLIC' | :guest | false | false | 'Composer package index' | :success
+ 'PUBLIC' | :anonymous | false | true | 'Composer package index' | :success
+ 'PRIVATE' | :developer | true | true | 'Composer package index' | :success
+ 'PRIVATE' | :guest | true | true | 'Composer package index' | :success
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
+ end
+
+ with_them do
+ include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown group id'
+ end
+ end
+
+ describe 'GET /api/v4/group/:id/-/packages/composer/p/:sha.json' do
+ let(:sha) { '123' }
+ let(:url) { "/group/#{group.id}/-/packages/composer/p/#{sha}.json" }
+ let!(:package) { create(:composer_package, :with_metadatum, project: project) }
+
+ subject { get api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | true | true | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | true | false | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | true | false | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | false | true | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | false | true | 'Composer provider index' | :success
+ 'PUBLIC' | :developer | false | false | 'Composer provider index' | :success
+ 'PUBLIC' | :guest | false | false | 'Composer provider index' | :success
+ 'PUBLIC' | :anonymous | false | true | 'Composer provider index' | :success
+ 'PRIVATE' | :developer | true | true | 'Composer provider index' | :success
+ 'PRIVATE' | :guest | true | true | 'Composer empty provider index' | :success
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
+ end
+
+ with_them do
+ include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown group id'
+ end
+ end
+
+ describe 'GET /api/v4/group/:id/-/packages/composer/*package_name.json' do
+ let(:package_name) { 'foobar' }
+ let(:url) { "/group/#{group.id}/-/packages/composer/#{package_name}.json" }
+
+ subject { get api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with no packages' do
+ include_context 'Composer user type', :developer, true do
+ it_behaves_like 'returning response status', :not_found
+ end
+ end
+
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | true | true | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | true | false | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | true | false | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | false | true | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | false | true | 'Composer package api request' | :success
+ 'PUBLIC' | :developer | false | false | 'Composer package api request' | :success
+ 'PUBLIC' | :guest | false | false | 'Composer package api request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'Composer package api request' | :success
+ 'PRIVATE' | :developer | true | true | 'Composer package api request' | :success
+ 'PRIVATE' | :guest | true | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
+ end
+
+ with_them do
+ include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown group id'
+ end
+ end
+
+ describe 'POST /api/v4/projects/:id/packages/composer' do
+ let(:url) { "/projects/#{project.id}/packages/composer" }
+ let(:params) { {} }
+
+ before(:all) do
+ project.repository.add_tag(user, 'v1.2.99', 'master')
+ end
+
+ subject { post api(url), headers: headers, params: params }
+
+ shared_examples 'composer package publish' do
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'Composer package creation' | :created
+ 'PUBLIC' | :guest | true | true | 'process Composer api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process Composer api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process Composer api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process Composer api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'Composer package creation' | :created
+ 'PRIVATE' | :guest | true | true | 'process Composer api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process Composer api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :unauthorized
+ end
+
+ with_them do
+ include_context 'Composer api project access', params[:project_visibility_level], params[:user_role], params[:user_token] do
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown project id'
+ end
+ end
+
+ context 'with no tag or branch params' do
+ let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :developer, :bad_request
+ end
+
+ context 'with a tag' do
+ context 'with an existing branch' do
+ let(:params) { { tag: 'v1.2.99' } }
+
+ it_behaves_like 'composer package publish'
+ end
+
+ context 'with a non existing tag' do
+ let(:params) { { tag: 'non-existing-tag' } }
+ let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :developer, :not_found
+ end
+ end
+
+ context 'with a branch' do
+ context 'with an existing branch' do
+ let(:params) { { branch: 'master' } }
+
+ it_behaves_like 'composer package publish'
+ end
+
+ context 'with a non existing branch' do
+ let(:params) { { branch: 'non-existing-branch' } }
+ let(:headers) { build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :developer, :not_found
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/composer/archives/*package_name?sha=:sha' do
+ let(:sha) { '123' }
+ let(:url) { "/projects/#{project.id}/packages/composer/archives/#{package_name}.zip" }
+ let(:params) { { sha: sha } }
+
+ subject { get api(url), headers: headers, params: params }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
+
+ context 'when the sha does not match the package name' do
+ let(:sha) { '123' }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'when the package name does not match the sha' do
+ let(:branch) { project.repository.find_branch('master') }
+ let(:sha) { branch.target }
+ let(:url) { "/projects/#{project.id}/packages/composer/archives/unexisting-package-name.zip" }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'with a match package name and sha' do
+ let(:branch) { project.repository.find_branch('master') }
+ let(:sha) { branch.target }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :expected_status) do
+ 'PUBLIC' | :developer | true | true | :success
+ 'PUBLIC' | :guest | true | true | :success
+ 'PUBLIC' | :developer | true | false | :success
+ 'PUBLIC' | :guest | true | false | :success
+ 'PUBLIC' | :developer | false | true | :success
+ 'PUBLIC' | :guest | false | true | :success
+ 'PUBLIC' | :developer | false | false | :success
+ 'PUBLIC' | :guest | false | false | :success
+ 'PUBLIC' | :anonymous | false | true | :success
+ 'PRIVATE' | :developer | true | true | :success
+ 'PRIVATE' | :guest | true | true | :success
+ 'PRIVATE' | :developer | true | false | :success
+ 'PRIVATE' | :guest | true | false | :success
+ 'PRIVATE' | :developer | false | true | :success
+ 'PRIVATE' | :guest | false | true | :success
+ 'PRIVATE' | :developer | false | false | :success
+ 'PRIVATE' | :guest | false | false | :success
+ 'PRIVATE' | :anonymous | false | true | :success
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like 'process Composer api request', params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+ end
+
+ it_behaves_like 'rejects Composer access with unknown project id'
+ end
+ end
+end
diff --git a/spec/requests/api/conan_packages_spec.rb b/spec/requests/api/conan_packages_spec.rb
new file mode 100644
index 00000000000..1d88eaef79c
--- /dev/null
+++ b/spec/requests/api/conan_packages_spec.rb
@@ -0,0 +1,840 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::ConanPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+
+ let(:package) { create(:conan_package) }
+ let_it_be(:personal_access_token) { create(:personal_access_token) }
+ let_it_be(:user) { personal_access_token.user }
+ let(:project) { package.project }
+
+ let(:base_secret) { SecureRandom.base64(64) }
+ let(:auth_token) { personal_access_token.token }
+ let(:job) { create(:ci_build, user: user) }
+ let(:job_token) { job.token }
+ let(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ let(:headers) do
+ { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials('foo', auth_token) }
+ end
+
+ let(:jwt_secret) do
+ OpenSSL::HMAC.hexdigest(
+ OpenSSL::Digest::SHA256.new,
+ base_secret,
+ Gitlab::ConanToken::HMAC_KEY
+ )
+ end
+
+ before do
+ project.add_developer(user)
+ allow(Settings).to receive(:attr_encrypted_db_key_base).and_return(base_secret)
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/ping' do
+ it 'responds with 401 Unauthorized when no token provided' do
+ get api('/packages/conan/v1/ping')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 200 OK when valid token is provided' do
+ jwt = build_jwt(personal_access_token)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
+ end
+
+ it 'responds with 200 OK when valid job token is provided' do
+ jwt = build_jwt_from_job(job)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
+ end
+
+ it 'responds with 200 OK when valid deploy token is provided' do
+ jwt = build_jwt_from_deploy_token(deploy_token)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
+ end
+
+ it 'responds with 401 Unauthorized when invalid access token ID is provided' do
+ jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when invalid user is provided' do
+ jwt = build_jwt(personal_access_token, user_id: 12345)
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do
+ jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32))
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when invalid JWT is provided' do
+ get api('/packages/conan/v1/ping'), headers: build_token_auth_header('invalid-jwt')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ context 'packages feature disabled' do
+ it 'responds with 404 Not Found' do
+ stub_packages_setting(enabled: false)
+ get api('/packages/conan/v1/ping')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/search' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ get api('/packages/conan/v1/conans/search'), headers: headers, params: params
+ end
+
+ subject { json_response['results'] }
+
+ context 'returns packages with a matching name' do
+ let(:params) { { q: package.conan_recipe } }
+
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+
+ context 'returns packages using a * wildcard' do
+ let(:params) { { q: "#{package.name[0, 3]}*" } }
+
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+
+ context 'does not return non-matching packages' do
+ let(:params) { { q: "foo" } }
+
+ it { is_expected.to be_blank }
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/users/authenticate' do
+ subject { get api('/packages/conan/v1/users/authenticate'), headers: headers }
+
+ context 'when using invalid token' do
+ let(:auth_token) { 'invalid_token' }
+
+ it 'responds with 401' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when valid JWT access token is provided' do
+ it 'responds with 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'token has valid validity time' do
+ Timecop.freeze do
+ subject
+
+ payload = JSONWebToken::HMACToken.decode(
+ response.body, jwt_secret).first
+ expect(payload['access_token']).to eq(personal_access_token.id)
+ expect(payload['user_id']).to eq(personal_access_token.user_id)
+
+ duration = payload['exp'] - payload['iat']
+ expect(duration).to eq(1.hour)
+ end
+ end
+ end
+
+ context 'with valid job token' do
+ let(:auth_token) { job_token }
+
+ it 'responds with 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with valid deploy token' do
+ let(:auth_token) { deploy_token.token }
+
+ it 'responds with 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/users/check_credentials' do
+ it 'responds with a 200 OK with PAT' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with job token' do
+ let(:auth_token) { job_token }
+
+ it 'responds with a 200 OK with job token' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with deploy token' do
+ let(:auth_token) { deploy_token.token }
+
+ it 'responds with a 200 OK with job token' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'responds with a 401 Unauthorized when an invalid token is used' do
+ get api('/packages/conan/v1/users/check_credentials'), headers: build_token_auth_header('invalid-token')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ shared_examples 'rejects invalid recipe' do
+ context 'with invalid recipe path' do
+ let(:recipe_path) { '../../foo++../..' }
+
+ it 'returns 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ shared_examples 'rejects recipe for invalid project' do
+ context 'with invalid recipe path' do
+ let(:recipe_path) { 'aa/bb/not-existing-project/ccc' }
+
+ it 'returns forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ shared_examples 'rejects recipe for not found package' do
+ context 'with invalid recipe path' do
+ let(:recipe_path) do
+ 'aa/bb/%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) }
+ end
+
+ it 'returns not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ shared_examples 'empty recipe for not found package' do
+ context 'with invalid recipe url' do
+ let(:recipe_path) do
+ 'aa/bb/%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) }
+ end
+
+ it 'returns not found' do
+ allow(::Packages::Conan::PackagePresenter).to receive(:new)
+ .with(
+ 'aa/bb@%{project}/ccc' % { project: ::Packages::Conan::Metadatum.package_username_from(full_path: project.full_path) },
+ user,
+ project,
+ any_args
+ ).and_return(presenter)
+ allow(presenter).to receive(:recipe_snapshot) { {} }
+ allow(presenter).to receive(:package_snapshot) { {} }
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq("{}")
+ end
+ end
+ end
+
+ shared_examples 'recipe download_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ it 'returns the download_urls for the recipe files' do
+ expected_response = {
+ 'conanfile.py' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py",
+ 'conanmanifest.txt' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
+ }
+
+ allow(presenter).to receive(:recipe_urls) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+
+ shared_examples 'package download_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ it 'returns the download_urls for the package files' do
+ expected_response = {
+ 'conaninfo.txt' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt",
+ 'conanmanifest.txt' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conanmanifest.txt",
+ 'conan_package.tgz' => "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conan_package.tgz"
+ }
+
+ allow(presenter).to receive(:package_urls) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+
+ context 'recipe endpoints' do
+ let(:jwt) { build_jwt(personal_access_token) }
+ let(:headers) { build_token_auth_header(jwt.encoded) }
+ let(:conan_package_reference) { '123456789' }
+ let(:presenter) { double('::Packages::Conan::PackagePresenter') }
+
+ before do
+ allow(::Packages::Conan::PackagePresenter).to receive(:new)
+ .with(package.conan_recipe, user, package.project, any_args)
+ .and_return(presenter)
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'empty recipe for not found package'
+
+ context 'with existing package' do
+ it 'returns a hash of files with their md5 hashes' do
+ expected_response = {
+ 'conanfile.py' => 'md5hash1',
+ 'conanmanifest.txt' => 'md5hash2'
+ }
+
+ allow(presenter).to receive(:recipe_snapshot) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'empty recipe for not found package'
+
+ context 'with existing package' do
+ it 'returns a hash of md5 values for the files' do
+ expected_response = {
+ 'conaninfo.txt' => "md5hash1",
+ 'conanmanifest.txt' => "md5hash2",
+ 'conan_package.tgz' => "md5hash3"
+ }
+
+ allow(presenter).to receive(:package_snapshot) { expected_response }
+
+ subject
+
+ expect(json_response).to eq(expected_response)
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/digest' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/digest"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'recipe download_urls'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/download_urls' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'package download_urls'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/download_urls' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/download_urls"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'recipe download_urls'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/digest' do
+ subject { get api("/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest"), headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'rejects recipe for invalid project'
+ it_behaves_like 'package download_urls'
+ end
+
+ describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/upload_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ let(:params) do
+ { "conanfile.py": 24,
+ "conanmanifext.txt": 123 }
+ end
+
+ subject { post api("/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params, headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+
+ it 'returns a set of upload urls for the files requested' do
+ subject
+
+ expected_response = {
+ 'conanfile.py': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py",
+ 'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt"
+ }
+
+ expect(response.body).to eq(expected_response.to_json)
+ end
+ end
+
+ describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/upload_urls' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ let(:params) do
+ { "conaninfo.txt": 24,
+ "conanmanifext.txt": 123,
+ "conan_package.tgz": 523 }
+ end
+
+ subject { post api("/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params, headers: headers }
+
+ it_behaves_like 'rejects invalid recipe'
+
+ it 'returns a set of upload urls for the files requested' do
+ expected_response = {
+ 'conaninfo.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt",
+ 'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conanmanifest.txt",
+ 'conan_package.tgz': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conan_package.tgz"
+ }
+
+ subject
+
+ expect(response.body).to eq(expected_response.to_json)
+ end
+ end
+
+ describe 'DELETE /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do
+ let(:recipe_path) { package.conan_recipe_path }
+
+ subject { delete api("/packages/conan/v1/conans/#{recipe_path}"), headers: headers}
+
+ it_behaves_like 'rejects invalid recipe'
+
+ it 'returns unauthorized for users without valid permission' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'with delete permissions' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'delete_package'
+
+ it 'deletes a package' do
+ expect { subject }.to change { Packages::Package.count }.from(2).to(1)
+ end
+ end
+ end
+ end
+
+ context 'file endpoints' do
+ let(:jwt) { build_jwt(personal_access_token) }
+ let(:headers) { build_token_auth_header(jwt.encoded) }
+ let(:recipe_path) { package.conan_recipe_path }
+
+ shared_examples 'denies download with no token' do
+ context 'with no private token' do
+ let(:headers) { {} }
+
+ it 'returns 400' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ shared_examples 'a public project with packages' do
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'an internal project with packages' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'denies download with no token'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'a private project with packages' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'denies download with no token'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ shared_examples 'a project is not found' do
+ let(:recipe_path) { 'not/package/for/project' }
+
+ it 'returns forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
+:recipe_revision/export/:file_name' do
+ let(:recipe_file) { package.package_files.find_by(file_name: 'conanfile.py') }
+ let(:metadata) { recipe_file.conan_file_metadatum }
+
+ subject do
+ get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}"),
+ headers: headers
+ end
+
+ it_behaves_like 'a public project with packages'
+ it_behaves_like 'an internal project with packages'
+ it_behaves_like 'a private project with packages'
+ it_behaves_like 'a project is not found'
+ end
+
+ describe 'GET /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/
+:recipe_revision/package/:conan_package_reference/:package_revision/:file_name' do
+ let(:package_file) { package.package_files.find_by(file_name: 'conaninfo.txt') }
+ let(:metadata) { package_file.conan_file_metadatum }
+
+ subject do
+ get api("/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}"),
+ headers: headers
+ end
+
+ it_behaves_like 'a public project with packages'
+ it_behaves_like 'an internal project with packages'
+ it_behaves_like 'a private project with packages'
+ it_behaves_like 'a project is not found'
+
+ context 'tracking the conan_package.tgz download' do
+ let(:package_file) { package.package_files.find_by(file_name: ::Packages::Conan::FileMetadatum::PACKAGE_BINARY) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+ end
+ end
+
+ context 'file uploads' do
+ let(:jwt) { build_jwt(personal_access_token) }
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:headers_with_token) { build_token_auth_header(jwt.encoded).merge(workhorse_header) }
+ let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"}
+
+ shared_examples 'uploads a package file' do
+ context 'with object storage disabled' do
+ context 'without a file from workhorse' do
+ let(:params) { { file: nil } }
+
+ it_behaves_like 'package workhorse uploads'
+
+ it 'rejects the request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'without a token' do
+ it 'rejects request without a token' do
+ headers_with_token.delete('HTTP_AUTHORIZATION')
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when params from workhorse are correct' do
+ it 'creates package and stores package file' do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq(params[:file].original_filename)
+ end
+
+ it "doesn't attempt to migrate file to object storage" do
+ expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
+
+ subject
+ end
+ end
+ end
+
+ context 'with object storage enabled' do
+ context 'and direct upload enabled' do
+ let!(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ file: fog_file,
+ 'file.remote_id' => remote_id
+ }
+ end
+
+ it 'responds with status 403' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'with valid remote_id' do
+ let(:params) do
+ {
+ file: fog_file,
+ 'file.remote_id' => file_name
+ }
+ end
+
+ it 'creates package and stores package file' do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq(params[:file].original_filename)
+ expect(package_file.file.read).to eq('content')
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+ end
+
+ shared_examples 'workhorse authorization' do
+ it 'authorizes posting package with a valid token' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ it 'rejects request without a valid token' do
+ headers_with_token['HTTP_AUTHORIZATION'] = 'foo'
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'rejects request without a valid permission' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'rejects requests that bypassed gitlab-workhorse' do
+ headers_with_token.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'when using remote storage' do
+ context 'when direct upload is enabled' do
+ before do
+ stub_package_file_object_storage(enabled: true, direct_upload: true)
+ end
+
+ it 'responds with status 200, location of package remote store and object details' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response).not_to have_key('TempPath')
+ expect(json_response['RemoteObject']).to have_key('ID')
+ expect(json_response['RemoteObject']).to have_key('GetURL')
+ expect(json_response['RemoteObject']).to have_key('StoreURL')
+ expect(json_response['RemoteObject']).to have_key('DeleteURL')
+ expect(json_response['RemoteObject']).not_to have_key('MultipartUpload')
+ end
+ end
+
+ context 'when direct upload is disabled' do
+ before do
+ stub_package_file_object_storage(enabled: true, direct_upload: false)
+ end
+
+ it 'handles as a local file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).to eq(::Packages::PackageFileUploader.workhorse_local_upload_path)
+ expect(json_response['RemoteObject']).to be_nil
+ end
+ end
+ end
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name/authorize' do
+ subject { put api("/packages/conan/v1/files/#{recipe_path}/0/export/conanfile.py/authorize"), headers: headers_with_token }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'workhorse authorization'
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name/authorize' do
+ subject { put api("/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/conaninfo.txt/authorize"), headers: headers_with_token }
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'workhorse authorization'
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do
+ let(:file_name) { 'conanfile.py' }
+ let(:params) { { file: temp_file(file_name) } }
+
+ subject do
+ workhorse_finalize(
+ "/api/v4/packages/conan/v1/files/#{recipe_path}/0/export/#{file_name}",
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: headers_with_token
+ )
+ end
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'uploads a package file'
+ end
+
+ describe 'PUT /api/v4/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:conan_package_reference/:package_revision/:file_name' do
+ let(:file_name) { 'conaninfo.txt' }
+ let(:params) { { file: temp_file(file_name) } }
+
+ subject do
+ workhorse_finalize(
+ "/api/v4/packages/conan/v1/files/#{recipe_path}/0/package/123456789/0/#{file_name}",
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: headers_with_token
+ )
+ end
+
+ it_behaves_like 'rejects invalid recipe'
+ it_behaves_like 'uploads a package file'
+ context 'tracking the conan_package.tgz upload' do
+ let(:file_name) { ::Packages::Conan::FileMetadatum::PACKAGE_BINARY }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/container_registry_event_spec.rb b/spec/requests/api/container_registry_event_spec.rb
index 2cdf2656cb7..4d38ddddffd 100644
--- a/spec/requests/api/container_registry_event_spec.rb
+++ b/spec/requests/api/container_registry_event_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ContainerRegistryEvent do
+RSpec.describe API::ContainerRegistryEvent do
let(:secret_token) { 'secret_token' }
let(:events) { [{ action: 'push' }] }
let(:registry_headers) { { 'Content-Type' => ::API::ContainerRegistryEvent::DOCKER_DISTRIBUTION_EVENTS_V1_JSON } }
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index e8cc6bc71ae..81cef653770 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::DeployKeys do
+RSpec.describe API::DeployKeys do
let(:user) { create(:user) }
let(:maintainer) { create(:user) }
let(:admin) { create(:admin) }
diff --git a/spec/requests/api/deploy_tokens_spec.rb b/spec/requests/api/deploy_tokens_spec.rb
index 2b86d59fbba..8ec4f888e2e 100644
--- a/spec/requests/api/deploy_tokens_spec.rb
+++ b/spec/requests/api/deploy_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::DeployTokens do
+RSpec.describe API::DeployTokens do
let_it_be(:user) { create(:user) }
let_it_be(:creator) { create(:user) }
let_it_be(:project) { create(:project, creator_id: creator.id) }
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index ef2415a0cde..8113de96ac4 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Deployments do
+RSpec.describe API::Deployments do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index cb3efb2cf5f..720ea429c2c 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Discussions do
+RSpec.describe API::Discussions do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, :repository, namespace: user.namespace) }
let(:private_user) { create(:user) }
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index a25a6485f47..f16cd58bb34 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'doorkeeper access' do
+RSpec.describe 'doorkeeper access' do
let!(:user) { create(:user) }
let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" }
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 91b3dd93433..b1ac8f9eeec 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Environments do
+RSpec.describe API::Environments do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:project) { create(:project, :private, :repository, namespace: user.namespace) }
diff --git a/spec/requests/api/error_tracking_spec.rb b/spec/requests/api/error_tracking_spec.rb
index deed9777025..8c9ca1b6a9d 100644
--- a/spec/requests/api/error_tracking_spec.rb
+++ b/spec/requests/api/error_tracking_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ErrorTracking do
+RSpec.describe API::ErrorTracking do
let_it_be(:user) { create(:user) }
let(:setting) { create(:project_error_tracking_setting) }
let(:project) { setting.project }
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
index 58a55c2e6d0..6a8d5f91abd 100644
--- a/spec/requests/api/events_spec.rb
+++ b/spec/requests/api/events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Events do
+RSpec.describe API::Events do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 59a9ed2f77d..2746e777306 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Features, stub_feature_flags: false do
+RSpec.describe API::Features, stub_feature_flags: false do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 198e4f64bcc..b50f63ed67c 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe API::Files do
+RSpec.describe API::Files do
+ include RepoHelpers
+
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace ) }
let(:guest) { create(:user) { |u| project.add_guest(u) } }
@@ -183,6 +185,26 @@ describe API::Files do
expect(response.content_type).to eq('application/json')
end
+ context 'with filename with pathspec characters' do
+ let(:file_path) { ':wq' }
+ let(:newrev) { project.repository.commit('master').sha }
+
+ before do
+ create_file_in_repo(project, 'master', 'master', file_path, 'Test file')
+ end
+
+ it 'returns JSON wth commit SHA' do
+ params[:ref] = 'master'
+
+ get api(route(file_path), api_user), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['file_path']).to eq(file_path)
+ expect(json_response['file_name']).to eq(file_path)
+ expect(json_response['last_commit_id']).to eq(newrev)
+ end
+ end
+
it 'returns file by commit sha' do
# This file is deleted on HEAD
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
diff --git a/spec/requests/api/freeze_periods_spec.rb b/spec/requests/api/freeze_periods_spec.rb
index 0b7828ebedf..5589d4d543d 100644
--- a/spec/requests/api/freeze_periods_spec.rb
+++ b/spec/requests/api/freeze_periods_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::FreezePeriods do
+RSpec.describe API::FreezePeriods do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb
new file mode 100644
index 00000000000..91e455dac19
--- /dev/null
+++ b/spec/requests/api/go_proxy_spec.rb
@@ -0,0 +1,465 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::GoProxy do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
+ let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
+
+ let_it_be(:oauth) { create :oauth_access_token, scopes: 'api', resource_owner: user }
+ let_it_be(:job) { create :ci_build, user: user }
+ let_it_be(:pa_token) { create :personal_access_token, user: user }
+
+ let_it_be(:modules) do
+ commits = [
+ create(:go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'README.md' => 'Hi' } ),
+ create(:go_module_commit, :module, project: project, tag: 'v1.0.1' ),
+ create(:go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg' ),
+ create(:go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod' ),
+ create(:go_module_commit, :files, project: project, files: { 'y.go' => "package a\n" } ),
+ create(:go_module_commit, :module, project: project, name: 'v2' ),
+ create(:go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" })
+ ]
+
+ { sha: [commits[4].sha, commits[5].sha] }
+ end
+
+ before do
+ project.add_developer(user)
+
+ stub_feature_flags(go_proxy_disable_gomod_validation: false)
+
+ modules
+ end
+
+ shared_examples 'an unavailable resource' do
+ it 'returns not found' do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'a module version list resource' do |*versions, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "list" }
+
+ it "returns #{versions.empty? ? 'nothing' : versions.join(', ')}" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body.split("\n").to_set).to eq(versions.to_set)
+ end
+ end
+
+ shared_examples 'a missing module version list resource' do |path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "list" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module version information resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.info" }
+
+ it "returns information for #{version}" do
+ get_resource(user)
+
+ time = project.repository.find_tag(version).dereferenced_target.committed_date
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_kind_of(Hash)
+ expect(json_response['Version']).to eq(version)
+ expect(json_response['Time']).to eq(time.strftime('%Y-%m-%dT%H:%M:%S.%L%:z'))
+ end
+ end
+
+ shared_examples 'a missing module version information resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.info" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module pseudo-version information resource' do |prefix, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:commit) { project.repository.commit_by(oid: sha) }
+ let(:version) { fmt_pseudo_version prefix, commit }
+ let(:resource) { "#{version}.info" }
+
+ it "returns information for #{prefix}yyyymmddhhmmss-abcdefabcdef" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_kind_of(Hash)
+ expect(json_response['Version']).to eq(version)
+ expect(json_response['Time']).to eq(commit.committed_date.strftime('%Y-%m-%dT%H:%M:%S.%L%:z'))
+ end
+ end
+
+ shared_examples 'a missing module pseudo-version information resource' do |path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:commit) do
+ raise "tried to reference :commit without defining :sha" unless defined?(sha)
+
+ project.repository.commit_by(oid: sha)
+ end
+ let(:resource) { "#{version}.info" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module file resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.mod" }
+
+ it "returns #{path}/go.mod from the repo" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body.split("\n", 2).first).to eq("module #{module_name}")
+ end
+ end
+
+ shared_examples 'a missing module file resource' do |version, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.mod" }
+
+ it_behaves_like 'an unavailable resource'
+ end
+
+ shared_examples 'a module archive resource' do |version, entries, path: ''|
+ let(:module_name) { "#{base}#{path}" }
+ let(:resource) { "#{version}.zip" }
+
+ it "returns an archive of #{path.empty? ? '/' : path} @ #{version} from the repo" do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ entries = entries.map { |e| "#{module_name}@#{version}/#{e}" }.to_set
+ actual = Set[]
+ Zip::InputStream.open(StringIO.new(response.body)) do |zip|
+ while (entry = zip.get_next_entry)
+ actual.add(entry.name)
+ end
+ end
+
+ expect(actual).to eq(entries)
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ context 'for the root module' do
+ it_behaves_like 'a module version list resource', 'v1.0.1', 'v1.0.2', 'v1.0.3'
+ end
+
+ context 'for the package' do
+ it_behaves_like 'a module version list resource', path: '/pkg'
+ end
+
+ context 'for the submodule' do
+ it_behaves_like 'a module version list resource', 'v1.0.3', path: '/mod'
+ end
+
+ context 'for the root module v2' do
+ it_behaves_like 'a module version list resource', 'v2.0.0', path: '/v2'
+ end
+
+ context 'with a URL encoded relative path component' do
+ it_behaves_like 'a missing module version list resource', path: '/%2E%2E%2Fxyz'
+ end
+
+ context 'with the feature disabled' do
+ before do
+ stub_feature_flags(go_proxy: false)
+ end
+
+ it_behaves_like 'a missing module version list resource'
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
+ context 'with the root module v1.0.1' do
+ it_behaves_like 'a module version information resource', 'v1.0.1'
+ end
+
+ context 'with the submodule v1.0.3' do
+ it_behaves_like 'a module version information resource', 'v1.0.3', path: '/mod'
+ end
+
+ context 'with the root module v2.0.0' do
+ it_behaves_like 'a module version information resource', 'v2.0.0', path: '/v2'
+ end
+
+ context 'with an invalid path' do
+ it_behaves_like 'a missing module version information resource', 'v1.0.3', path: '/pkg'
+ end
+
+ context 'with an invalid version' do
+ it_behaves_like 'a missing module version information resource', 'v1.0.1', path: '/mod'
+ end
+
+ context 'with a pseudo-version for v1' do
+ it_behaves_like 'a module pseudo-version information resource', 'v1.0.4-0.' do
+ let(:sha) { modules[:sha][0] }
+ end
+ end
+
+ context 'with a pseudo-version for v2' do
+ it_behaves_like 'a module pseudo-version information resource', 'v2.0.0-', path: '/v2' do
+ let(:sha) { modules[:sha][1] }
+ end
+ end
+
+ context 'with a pseudo-version with an invalid timestamp' do
+ it_behaves_like 'a missing module pseudo-version information resource' do
+ let(:version) { "v1.0.4-0.00000000000000-#{modules[:sha][0][0..11]}" }
+ end
+ end
+
+ context 'with a pseudo-version with an invalid commit sha' do
+ it_behaves_like 'a missing module pseudo-version information resource' do
+ let(:sha) { modules[:sha][0] }
+ let(:version) { "v1.0.4-0.#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-000000000000" }
+ end
+ end
+
+ context 'with a pseudo-version with a short commit sha' do
+ it_behaves_like 'a missing module pseudo-version information resource' do
+ let(:sha) { modules[:sha][0] }
+ let(:version) { "v1.0.4-0.#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{modules[:sha][0][0..10]}" }
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.mod' do
+ context 'with the root module v1.0.1' do
+ it_behaves_like 'a module file resource', 'v1.0.1'
+ end
+
+ context 'with the submodule v1.0.3' do
+ it_behaves_like 'a module file resource', 'v1.0.3', path: '/mod'
+ end
+
+ context 'with the root module v2.0.0' do
+ it_behaves_like 'a module file resource', 'v2.0.0', path: '/v2'
+ end
+
+ context 'with an invalid path' do
+ it_behaves_like 'a missing module file resource', 'v1.0.3', path: '/pkg'
+ end
+
+ context 'with an invalid version' do
+ it_behaves_like 'a missing module file resource', 'v1.0.1', path: '/mod'
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.zip' do
+ context 'with the root module v1.0.1' do
+ it_behaves_like 'a module archive resource', 'v1.0.1', ['README.md', 'go.mod', 'a.go']
+ end
+
+ context 'with the root module v1.0.2' do
+ it_behaves_like 'a module archive resource', 'v1.0.2', ['README.md', 'go.mod', 'a.go', 'pkg/b.go']
+ end
+
+ context 'with the root module v1.0.3' do
+ it_behaves_like 'a module archive resource', 'v1.0.3', ['README.md', 'go.mod', 'a.go', 'pkg/b.go']
+ end
+
+ context 'with the submodule v1.0.3' do
+ it_behaves_like 'a module archive resource', 'v1.0.3', ['go.mod', 'a.go'], path: '/mod'
+ end
+
+ context 'with the root module v2.0.0' do
+ it_behaves_like 'a module archive resource', 'v2.0.0', ['go.mod', 'a.go', 'x.go'], path: '/v2'
+ end
+ end
+
+ context 'with an invalid module directive' do
+ let_it_be(:project) { create :project_empty_repo, :public, creator: user }
+ let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
+
+ let_it_be(:modules) do
+ create(:go_module_commit, :files, project: project, files: { 'a.go' => "package\a" } )
+ create(:go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'go.mod' => "module not/a/real/module\n" })
+ create(:go_module_commit, :files, project: project, files: { 'v2/a.go' => "package a\n" } )
+ create(:go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/go.mod' => "module #{base}\n" } )
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ context 'with a completely wrong directive for v1' do
+ it_behaves_like 'a module version list resource'
+ end
+
+ context 'with a directive omitting the suffix for v2' do
+ it_behaves_like 'a module version list resource', path: '/v2'
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
+ context 'with a completely wrong directive for v1' do
+ it_behaves_like 'a missing module version information resource', 'v1.0.0'
+ end
+
+ context 'with a directive omitting the suffix for v2' do
+ it_behaves_like 'a missing module version information resource', 'v2.0.0', path: '/v2'
+ end
+ end
+ end
+
+ context 'with a case sensitive project and versions' do
+ let_it_be(:project) { create :project_empty_repo, :public, creator: user, path: 'MyGoLib' }
+ let_it_be(:base) { "#{Settings.build_gitlab_go_url}/#{project.full_path}" }
+ let_it_be(:base_encoded) { base.gsub(/[A-Z]/) { |s| "!#{s.downcase}"} }
+
+ let_it_be(:modules) do
+ create(:go_module_commit, :files, project: project, files: { 'README.md' => "Hi" })
+ create(:go_module_commit, :module, project: project, tag: 'v1.0.1-prerelease')
+ create(:go_module_commit, :package, project: project, tag: 'v1.0.1-Prerelease', path: 'pkg')
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ let(:resource) { "list" }
+
+ context 'with a case encoded path' do
+ it_behaves_like 'a module version list resource', 'v1.0.1-prerelease', 'v1.0.1-Prerelease' do
+ let(:module_name) { base_encoded }
+ end
+ end
+
+ context 'without a case encoded path' do
+ it_behaves_like 'a missing module version list resource' do
+ let(:module_name) { base.downcase }
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/:module_version.info' do
+ context 'with a case encoded path' do
+ it_behaves_like 'a module version information resource', 'v1.0.1-Prerelease' do
+ let(:module_name) { base_encoded }
+ let(:resource) { "v1.0.1-!prerelease.info" }
+ end
+ end
+
+ context 'without a case encoded path' do
+ it_behaves_like 'a module version information resource', 'v1.0.1-prerelease' do
+ let(:module_name) { base_encoded }
+ let(:resource) { "v1.0.1-prerelease.info" }
+ end
+ end
+ end
+ end
+
+ context 'with a private project' do
+ let(:module_name) { base }
+
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ let(:resource) { "list" }
+
+ it 'returns ok with an oauth token' do
+ get_resource(oauth_access_token: oauth)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns ok with a job token' do
+ get_resource(oauth_access_token: job)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns ok with a personal access token' do
+ get_resource(personal_access_token: pa_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns ok with a personal access token and basic authentication' do
+ get_resource(headers: build_basic_auth_header(user.username, pa_token.token))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns unauthorized with no authentication' do
+ get_resource
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context 'with a public project' do
+ let(:module_name) { base }
+
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ let(:resource) { "list" }
+
+ it 'returns ok with no authentication' do
+ get_resource
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with a non-existent project' do
+ def get_resource(user = nil, **params)
+ get api("/projects/not%2fa%2fproject/packages/go/#{base}/@v/list", user, params)
+ end
+
+ describe 'GET /projects/:id/packages/go/*module_name/@v/list' do
+ it 'returns not found with a user' do
+ get_resource(user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with an oauth token' do
+ get_resource(oauth_access_token: oauth)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with a job token' do
+ get_resource(oauth_access_token: job)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with a personal access token' do
+ get_resource(personal_access_token: pa_token)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns unauthorized with no authentication' do
+ get_resource
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ def get_resource(user = nil, headers: {}, **params)
+ get api("/projects/#{project.id}/packages/go/#{module_name}/@v/#{resource}", user, params), headers: headers
+ end
+
+ def fmt_pseudo_version(prefix, commit)
+ "#{prefix}#{commit.committed_date.strftime('%Y%m%d%H%M%S')}-#{commit.sha[0..11]}"
+ end
+end
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
index 3cc1468be02..8a89590c85a 100644
--- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'get board lists' do
+RSpec.describe 'get board lists' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/boards/boards_query_spec.rb b/spec/requests/api/graphql/boards/boards_query_spec.rb
index a17554aba21..50004e5a8a1 100644
--- a/spec/requests/api/graphql/boards/boards_query_spec.rb
+++ b/spec/requests/api/graphql/boards/boards_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'get list of boards' do
+RSpec.describe 'get list of boards' do
include GraphqlHelpers
include_context 'group and project boards query context'
diff --git a/spec/requests/api/graphql/current_user/todos_query_spec.rb b/spec/requests/api/graphql/current_user/todos_query_spec.rb
index 321e1062a96..e298de0df01 100644
--- a/spec/requests/api/graphql/current_user/todos_query_spec.rb
+++ b/spec/requests/api/graphql/current_user/todos_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query current user todos' do
+RSpec.describe 'Query current user todos' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/current_user_query_spec.rb b/spec/requests/api/graphql/current_user_query_spec.rb
index 2b38b8e98ab..dc832b42fa5 100644
--- a/spec/requests/api/graphql/current_user_query_spec.rb
+++ b/spec/requests/api/graphql/current_user_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project information' do
+RSpec.describe 'getting project information' do
include GraphqlHelpers
let(:query) do
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index 266c98d6f08..ee7dba545be 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'GitlabSchema configurations' do
+RSpec.describe 'GitlabSchema configurations' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/group/labels_query_spec.rb b/spec/requests/api/graphql/group/labels_query_spec.rb
index 6c34cbadf95..31556ffca30 100644
--- a/spec/requests/api/graphql/group/labels_query_spec.rb
+++ b/spec/requests/api/graphql/group/labels_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting group label information' do
+RSpec.describe 'getting group label information' do
include GraphqlHelpers
let_it_be(:group) { create(:group, :public) }
diff --git a/spec/requests/api/graphql/group/milestones_spec.rb b/spec/requests/api/graphql/group/milestones_spec.rb
index bad0024e7a3..380eaea17f8 100644
--- a/spec/requests/api/graphql/group/milestones_spec.rb
+++ b/spec/requests/api/graphql/group/milestones_spec.rb
@@ -2,21 +2,22 @@
require 'spec_helper'
-describe 'Milestones through GroupQuery' do
+RSpec.describe 'Milestones through GroupQuery' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:now) { Time.now }
- let_it_be(:group) { create(:group) }
- let_it_be(:milestone_1) { create(:milestone, group: group) }
- let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
- let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
- let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) }
- let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) }
-
- let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
describe 'Get list of milestones from a group' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:milestone_1) { create(:milestone, group: group) }
+ let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
+ let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
+ let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) }
+ let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) }
+
+ let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
+
context 'when the request is correct' do
before do
fetch_milestones(user)
@@ -72,21 +73,6 @@ describe 'Milestones through GroupQuery' do
submilestone_1.to_global_id.to_s, submilestone_2.to_global_id.to_s
)
end
-
- context 'when group_milestone_descendants is disabled' do
- before do
- stub_feature_flags(group_milestone_descendants: false)
- end
-
- it 'ignores descendant milestones' do
- fetch_milestones(user, args)
-
- expect_array_response(
- milestone_1.to_global_id.to_s, milestone_2.to_global_id.to_s,
- milestone_3.to_global_id.to_s, milestone_4.to_global_id.to_s
- )
- end
- end
end
def fetch_milestones(user = nil, args = {})
@@ -120,4 +106,89 @@ describe 'Milestones through GroupQuery' do
node_array(milestone_data, extract_attribute)
end
end
+
+ describe 'ensures each field returns the correct value' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:milestone) { create(:milestone, group: group, start_date: now, due_date: now + 1.day) }
+ let_it_be(:open_issue) { create(:issue, project: project, milestone: milestone) }
+ let_it_be(:closed_issue) { create(:issue, :closed, project: project, milestone: milestone) }
+
+ let(:milestone_query) do
+ %{
+ id
+ title
+ description
+ state
+ webPath
+ dueDate
+ startDate
+ createdAt
+ updatedAt
+ projectMilestone
+ groupMilestone
+ subgroupMilestone
+ }
+ end
+
+ def post_query
+ full_query = graphql_query_for("group",
+ { full_path: group.full_path },
+ [query_graphql_field("milestones", nil, "nodes { #{milestone_query} }")]
+ )
+
+ post_graphql(full_query, current_user: user)
+
+ graphql_data.dig('group', 'milestones', 'nodes', 0)
+ end
+
+ it 'returns correct values for scalar fields' do
+ expect(post_query).to eq({
+ 'id' => global_id_of(milestone),
+ 'title' => milestone.title,
+ 'description' => milestone.description,
+ 'state' => 'active',
+ 'webPath' => milestone_path(milestone),
+ 'dueDate' => milestone.due_date.iso8601,
+ 'startDate' => milestone.start_date.iso8601,
+ 'createdAt' => milestone.created_at.iso8601,
+ 'updatedAt' => milestone.updated_at.iso8601,
+ 'projectMilestone' => false,
+ 'groupMilestone' => true,
+ 'subgroupMilestone' => false
+ })
+ end
+
+ context 'milestone statistics' do
+ let(:milestone_query) do
+ %{
+ stats {
+ totalIssuesCount
+ closedIssuesCount
+ }
+ }
+ end
+
+ it 'returns the correct milestone statistics' do
+ expect(post_query).to eq({
+ 'stats' => {
+ 'totalIssuesCount' => 2,
+ 'closedIssuesCount' => 1
+ }
+ })
+ end
+
+ context 'when the graphql_milestone_stats feature flag is disabled' do
+ before do
+ stub_feature_flags(graphql_milestone_stats: false)
+ end
+
+ it 'returns nil for the stats field' do
+ expect(post_query).to eq({
+ 'stats' => nil
+ })
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb
index c7b537a9923..d99bff2e349 100644
--- a/spec/requests/api/graphql/group_query_spec.rb
+++ b/spec/requests/api/graphql/group_query_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# Based on spec/requests/api/groups_spec.rb
# Should follow closely in order to ensure all situations are covered
-describe 'getting group information', :do_not_mock_admin_mode do
+RSpec.describe 'getting group information', :do_not_mock_admin_mode do
include GraphqlHelpers
include UploadHelpers
diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb
index 4c56c559cf9..6344ec371c8 100644
--- a/spec/requests/api/graphql/metadata_query_spec.rb
+++ b/spec/requests/api/graphql/metadata_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project information' do
+RSpec.describe 'getting project information' do
include GraphqlHelpers
let(:query) { graphql_query_for('metadata', {}, all_graphql_fields_for('Metadata')) }
diff --git a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
index cb35411b7a5..c47920087dc 100644
--- a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
+++ b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting Metrics Dashboard Annotations' do
+RSpec.describe 'Getting Metrics Dashboard Annotations' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
index d9d9ea9ad61..456b0a5dea1 100644
--- a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
+++ b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting Metrics Dashboard' do
+RSpec.describe 'Getting Metrics Dashboard' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -62,12 +62,12 @@ describe 'Getting Metrics Dashboard' do
context 'invalid dashboard' do
let(:path) { '.gitlab/dashboards/metrics.yml' }
- let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndasboard: ''" }) }
+ let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: can't be blank"])
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"])
end
end
@@ -78,7 +78,7 @@ describe 'Getting Metrics Dashboard' do
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: can't be blank"])
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: should be an array of panel_groups objects"])
end
end
end
diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb
index 9ebb57f6b9c..f79bac6ae3b 100644
--- a/spec/requests/api/graphql/multiplexed_queries_spec.rb
+++ b/spec/requests/api/graphql/multiplexed_queries_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'Multiplexed queries' do
+RSpec.describe 'Multiplexed queries' do
include GraphqlHelpers
it 'returns responses for multiple queries' do
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 a5159da84f3..4ad35e7f0d1 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do
+RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do
include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
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 5b5b2ec8788..6141a172253 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Create an alert issue from an alert' do
+RSpec.describe 'Create an alert issue from an alert' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
index 6663281e093..cd5cefa0a9a 100644
--- a/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/set_assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting assignees of an alert' do
+RSpec.describe 'Setting assignees of an alert' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb
new file mode 100644
index 00000000000..e5803f50474
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/todo/create_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Creating a todo for the alert' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let(:alert) { create(:alert_management_alert, project: project) }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: alert.iid.to_s
+ }
+ graphql_mutation(:alert_todo_create, variables) do
+ <<~QL
+ clientMutationId
+ errors
+ todo {
+ author {
+ username
+ }
+ }
+ QL
+ end
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:alert_todo_create) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'creates a todo for the current user' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['todo']['author']['username']).to eq(user.username)
+ end
+
+ context 'todo already exists' do
+ before do
+ create(:todo, :pending, project: project, user: user, target: alert)
+ end
+
+ it 'surfaces an error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to eq(['You already have pending todo for this alert'])
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
index 2a470bda689..ff55656a2ae 100644
--- a/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting the status of an alert' do
+RSpec.describe 'Setting the status of an alert' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
index 83dec7dd3e2..1891300dace 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding an AwardEmoji' do
+RSpec.describe 'Adding an AwardEmoji' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -15,11 +15,11 @@ describe 'Adding an AwardEmoji' do
name: emoji_name
}
- graphql_mutation(:add_award_emoji, variables)
+ graphql_mutation(:award_emoji_add, variables)
end
def mutation_response
- graphql_mutation_response(:add_award_emoji)
+ graphql_mutation_response(:award_emoji_add)
end
shared_examples 'a mutation that does not create an AwardEmoji' do
diff --git a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
index a2997db6cae..665b511abb8 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Removing an AwardEmoji' do
+RSpec.describe 'Removing an AwardEmoji' do
include GraphqlHelpers
let(:current_user) { create(:user) }
@@ -12,11 +12,11 @@ describe 'Removing an AwardEmoji' do
let(:input) { { awardable_id: GitlabSchema.id_from_object(awardable).to_s, name: emoji_name } }
let(:mutation) do
- graphql_mutation(:remove_award_emoji, input)
+ graphql_mutation(:award_emoji_remove, input)
end
def mutation_response
- graphql_mutation_response(:remove_award_emoji)
+ graphql_mutation_response(:award_emoji_remove)
end
def create_award_emoji(user)
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
index e1180c85c6b..ab4a213fde3 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Toggling an AwardEmoji' do
+RSpec.describe 'Toggling an AwardEmoji' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -15,11 +15,11 @@ describe 'Toggling an AwardEmoji' do
name: emoji_name
}
- graphql_mutation(:toggle_award_emoji, variables)
+ graphql_mutation(:award_emoji_toggle, variables)
end
def mutation_response
- graphql_mutation_response(:toggle_award_emoji)
+ graphql_mutation_response(:award_emoji_toggle)
end
shared_examples 'a mutation that does not create or destroy an AwardEmoji' do
diff --git a/spec/requests/api/graphql/mutations/branches/create_spec.rb b/spec/requests/api/graphql/mutations/branches/create_spec.rb
index b3c378ec2bc..082b445bf3e 100644
--- a/spec/requests/api/graphql/mutations/branches/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/branches/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creation of a new branch' do
+RSpec.describe 'Creation of a new branch' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/commits/create_spec.rb b/spec/requests/api/graphql/mutations/commits/create_spec.rb
index 10a69932948..9e4a96700bb 100644
--- a/spec/requests/api/graphql/mutations/commits/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/commits/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creation of a new commit' do
+RSpec.describe 'Creation of a new commit' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb
index bc256a08f00..bc1b42d68e6 100644
--- a/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/container_expiration_policy/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating the container expiration policy' do
+RSpec.describe 'Updating the container expiration policy' do
include GraphqlHelpers
using RSpec::Parameterized::TableSyntax
@@ -48,13 +48,48 @@ describe 'Updating the container expiration policy' do
end
end
- RSpec.shared_examples 'updating the container expiration policy' do
+ RSpec.shared_examples 'rejecting invalid regex for' do |field_name|
+ context "for field #{field_name}" do
+ let_it_be(:invalid_regex) { '*production' }
+ let(:params) do
+ {
+ :project_path => project.full_path,
+ field_name => invalid_regex
+ }
+ end
+
+ it_behaves_like 'returning response status', :success
+
+ it_behaves_like 'not creating the container expiration policy'
+
+ it 'returns an error' do
+ subject
+
+ expect(graphql_errors.size).to eq(1)
+ expect(graphql_errors.first['message']).to include("#{invalid_regex} is an invalid regexp")
+ end
+ end
+ end
+
+ RSpec.shared_examples 'accepting the mutation request updating the container expiration policy' do
it_behaves_like 'updating the container expiration policy attributes', mode: :update, from: { cadence: '1d', keep_n: 10, older_than: '90d' }, to: { cadence: '3month', keep_n: 100, older_than: '14d' }
it_behaves_like 'returning a success'
+
+ it_behaves_like 'rejecting invalid regex for', :name_regex
+ it_behaves_like 'rejecting invalid regex for', :name_regex_keep
+ end
+
+ RSpec.shared_examples 'accepting the mutation request creating the container expiration policy' do
+ it_behaves_like 'creating the container expiration policy'
+
+ it_behaves_like 'returning a success'
+
+ it_behaves_like 'rejecting invalid regex for', :name_regex
+ it_behaves_like 'rejecting invalid regex for', :name_regex_keep
end
- RSpec.shared_examples 'denying access to container expiration policy' do
+ RSpec.shared_examples 'denying the mutation request' do
it_behaves_like 'not creating the container expiration policy'
it_behaves_like 'returning response status', :success
@@ -71,11 +106,11 @@ describe 'Updating the container expiration policy' do
context 'with existing container expiration policy' do
where(:user_role, :shared_examples_name) do
- :maintainer | 'updating the container expiration policy'
- :developer | 'updating the container expiration policy'
- :reporter | 'denying access to container expiration policy'
- :guest | 'denying access to container expiration policy'
- :anonymous | 'denying access to container expiration policy'
+ :maintainer | 'accepting the mutation request updating the container expiration policy'
+ :developer | 'accepting the mutation request updating the container expiration policy'
+ :reporter | 'denying the mutation request'
+ :guest | 'denying the mutation request'
+ :anonymous | 'denying the mutation request'
end
with_them do
@@ -91,11 +126,11 @@ describe 'Updating the container expiration policy' do
let_it_be(:project, reload: true) { create(:project, :without_container_expiration_policy) }
where(:user_role, :shared_examples_name) do
- :maintainer | 'creating the container expiration policy'
- :developer | 'creating the container expiration policy'
- :reporter | 'denying access to container expiration policy'
- :guest | 'denying access to container expiration policy'
- :anonymous | 'denying access to container expiration policy'
+ :maintainer | 'accepting the mutation request creating the container expiration policy'
+ :developer | 'accepting the mutation request creating the container expiration policy'
+ :reporter | 'denying the mutation request'
+ :guest | 'denying the mutation request'
+ :anonymous | 'denying the mutation request'
end
with_them do
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 10376305b3e..e329416faee 100644
--- a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe "deleting designs" do
+RSpec.describe "deleting designs" do
include GraphqlHelpers
include DesignManagementTestHelpers
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 22adc064406..9a9c7107b20 100644
--- a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "spec_helper"
-describe "uploading designs" do
+RSpec.describe "uploading designs" do
include GraphqlHelpers
include DesignManagementTestHelpers
include WorkhorseHelpers
diff --git a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
index 95e967c039d..e83da830935 100644
--- a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
+++ b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Toggling the resolve status of a discussion' do
+RSpec.describe 'Toggling the resolve status of a discussion' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :public, :repository) }
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 4d0bb59b030..3f804a46992 100644
--- a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting an issue as confidential' do
+RSpec.describe 'Setting an issue as confidential' do
include GraphqlHelpers
let(:current_user) { create(:user) }
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 1efa9e16233..3dd1225db5a 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting Due Date of an issue' do
+RSpec.describe 'Setting Due Date of an issue' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
new file mode 100644
index 00000000000..f1d55430e02
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Setting an issue as locked' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:issue) { create(:issue) }
+ let_it_be(:project) { issue.project }
+ let(:input) { { locked: true } }
+
+ let(:mutation) do
+ variables = {
+ 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
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:issue_set_locked)
+ end
+
+ context 'when the user is not allowed to update the issue' do
+ it 'returns an error' do
+ error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).to include(a_hash_including('message' => error))
+ end
+ end
+
+ context 'when user is allowed to update the issue' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'updates the issue locked status' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['issue']['discussionLocked']).to be_truthy
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb b/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb
index be0d843d5ff..4057aa4ba9e 100644
--- a/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb
+++ b/spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Importing Jira Users' do
+RSpec.describe 'Importing Jira Users' do
include JiraServiceHelper
include GraphqlHelpers
diff --git a/spec/requests/api/graphql/mutations/jira_import/start_spec.rb b/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
index 296d33aec5d..e7124512ef1 100644
--- a/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
+++ b/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Starting a Jira Import' do
+RSpec.describe 'Starting a Jira Import' do
include JiraServiceHelper
include GraphqlHelpers
@@ -14,7 +14,8 @@ describe 'Starting a Jira Import' do
let(:mutation) do
variables = {
jira_project_key: jira_project_key,
- project_path: project_path
+ project_path: project_path,
+ users_mapping: [{ jiraAccountId: 'abc', gitlabId: 5 }]
}
graphql_mutation(:jira_import_start, variables)
diff --git a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
index 5c63f655f1d..d4ac639e226 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creation of a new merge request' do
+RSpec.describe 'Creation of a new merge request' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
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 8f908b7bf88..97873b01338 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting assignees of a merge request' do
+RSpec.describe 'Setting assignees of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
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 2112ff0dc74..34d347c76fd 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting labels of a merge request' do
+RSpec.describe 'Setting labels of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
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 c45da613591..a1a35bc1dcc 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting locked status of a merge request' do
+RSpec.describe 'Setting locked status of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
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 bd558edf9c5..d7e2602bd0a 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting milestone of a merge request' do
+RSpec.describe 'Setting milestone of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
index 975735bf246..6b3035fbf48 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_subscription_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting subscribed status of a merge request' do
+RSpec.describe 'Setting subscribed status of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
index 4492c51dbd7..2143abd3031 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Setting WIP status of a merge request' do
+RSpec.describe 'Setting Draft status of a merge request' do
include GraphqlHelpers
let(:current_user) { create(:user) }
@@ -41,39 +41,39 @@ describe 'Setting WIP status of a merge request' do
expect(graphql_errors).not_to be_empty
end
- it 'marks the merge request as WIP' do
+ it 'marks the merge request as Draft' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).to start_with('WIP:')
+ expect(mutation_response['mergeRequest']['title']).to start_with('Draft:')
end
- it 'does not do anything if the merge request was already marked `WIP`' do
- merge_request.update!(title: 'wip: hello world')
+ it 'does not do anything if the merge request was already marked `Draft`' do
+ merge_request.update!(title: 'draft: hello world')
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).to start_with('wip:')
+ expect(mutation_response['mergeRequest']['title']).to start_with('draft:')
end
- context 'when passing WIP false as input' do
+ context 'when passing Draft false as input' do
let(:input) { { wip: false } }
- it 'does not do anything if the merge reqeust was not marked wip' do
+ it 'does not do anything if the merge reqeust was not marked draft' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).not_to start_with(/wip\:/)
+ expect(mutation_response['mergeRequest']['title']).not_to start_with(/draft\:/)
end
- it 'unmarks the merge request as `WIP`' do
- merge_request.update!(title: 'wip: hello world')
+ it 'unmarks the merge request as `Draft`' do
+ merge_request.update!(title: 'draft: hello world')
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['mergeRequest']['title']).not_to start_with('/wip\:/')
+ expect(mutation_response['mergeRequest']['title']).not_to start_with('/draft\:/')
end
end
end
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
index 8568dc8ffc0..0e2da94f0f9 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Metrics::Dashboard::Annotations::Create do
+RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
index 217f538c53e..2459a6f3828 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Mutations::Metrics::Dashboard::Annotations::Delete do
+RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
index 4c535434faa..e847c46be1b 100644
--- a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding a DiffNote' do
+RSpec.describe 'Adding a DiffNote' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
index 0bba3e79434..896a398e308 100644
--- a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding an image DiffNote' do
+RSpec.describe 'Adding an image DiffNote' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
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 9a78d44245e..391ced7dc98 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Adding a Note' do
+RSpec.describe 'Adding a Note' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -13,7 +13,8 @@ describe 'Adding a Note' do
variables = {
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
discussion_id: (GitlabSchema.id_from_object(discussion).to_s if discussion),
- body: 'Body text'
+ body: 'Body text',
+ confidential: true
}
graphql_mutation(:create_note, variables)
@@ -40,6 +41,7 @@ describe 'Adding a Note' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['note']['body']).to eq('Body text')
+ expect(mutation_response['note']['confidential']).to eq(true)
end
describe 'creating Notes in reply to a discussion' do
diff --git a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
index 337a6e6f6e6..6002a5b5b9d 100644
--- a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Destroying a Note' do
+RSpec.describe 'Destroying a Note' do
include GraphqlHelpers
let!(:note) { create(:note) }
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 0362fef2d2e..f7be671e5f3 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating an image DiffNote' do
+RSpec.describe 'Updating an image DiffNote' do
include GraphqlHelpers
using RSpec::Parameterized::TableSyntax
diff --git a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
index a5c6b72005e..38378310d9f 100644
--- a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating a Note' do
+RSpec.describe 'Updating a Note' do
include GraphqlHelpers
let!(:note) { create(:note, note: original_body) }
diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
index 9052f54b171..e2474e1bcce 100644
--- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Creating a Snippet' do
+RSpec.describe 'Creating a Snippet' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
@@ -14,9 +14,8 @@ describe 'Creating a Snippet' do
let(:visibility_level) { 'public' }
let(:project_path) { nil }
let(:uploaded_files) { nil }
-
- let(:mutation) do
- variables = {
+ let(:mutation_vars) do
+ {
content: content,
description: description,
visibility_level: visibility_level,
@@ -25,8 +24,10 @@ describe 'Creating a Snippet' do
project_path: project_path,
uploaded_files: uploaded_files
}
+ end
- graphql_mutation(:create_snippet, variables)
+ let(:mutation) do
+ graphql_mutation(:create_snippet, mutation_vars)
end
def mutation_response
@@ -137,6 +138,47 @@ describe 'Creating a Snippet' do
end
end
+ context 'when snippet is created using the files param' do
+ let(:action) { :create }
+ let(:file_1) { { filePath: 'example_file1', content: 'This is the example file 1' }}
+ let(:file_2) { { filePath: 'example_file2', content: 'This is the example file 2' }}
+ let(:actions) { [{ action: action }.merge(file_1), { action: action }.merge(file_2)] }
+ let(:mutation_vars) do
+ {
+ description: description,
+ visibility_level: visibility_level,
+ project_path: project_path,
+ title: title,
+ files: actions
+ }
+ end
+
+ it 'creates the Snippet' do
+ expect do
+ subject
+ end.to change { Snippet.count }.by(1)
+ end
+
+ it 'returns the created Snippet' do
+ subject
+
+ expect(mutation_response['snippet']['title']).to eq(title)
+ expect(mutation_response['snippet']['description']).to eq(description)
+ expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
+ expect(mutation_response['snippet']['blobs'][0]['plainData']).to match(file_1[:content])
+ expect(mutation_response['snippet']['blobs'][0]['fileName']).to match(file_1[:file_path])
+ expect(mutation_response['snippet']['blobs'][1]['plainData']).to match(file_2[:content])
+ expect(mutation_response['snippet']['blobs'][1]['fileName']).to match(file_2[:file_path])
+ end
+
+ context 'when action is invalid' do
+ let(:file_1) { { filePath: 'example_file1' }}
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Snippet actions have invalid data']
+ it_behaves_like 'does not create snippet'
+ end
+ end
+
context 'when there are ActiveRecord validation errors' do
let(:title) { '' }
diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
index cb9aeea74b2..8ade72635af 100644
--- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Destroying a Snippet' do
+RSpec.describe 'Destroying a Snippet' do
include GraphqlHelpers
let(:current_user) { snippet.author }
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 6d4dce3f6f1..97e6ae8fda8 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Mark snippet as spam', :do_not_mock_admin_mode do
+RSpec.describe 'Mark snippet as spam', :do_not_mock_admin_mode do
include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index 968ea5aed52..3b2f9dc0f19 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Updating a Snippet' do
+RSpec.describe 'Updating a Snippet' do
include GraphqlHelpers
let_it_be(:original_content) { 'Initial content' }
@@ -16,8 +16,8 @@ describe 'Updating a Snippet' do
let(:current_user) { snippet.author }
let(:snippet_gid) { GitlabSchema.id_from_object(snippet).to_s }
- let(:mutation) do
- variables = {
+ let(:mutation_vars) do
+ {
id: snippet_gid,
content: updated_content,
description: updated_description,
@@ -25,8 +25,9 @@ describe 'Updating a Snippet' do
file_name: updated_file_name,
title: updated_title
}
-
- graphql_mutation(:update_snippet, variables)
+ end
+ let(:mutation) do
+ graphql_mutation(:update_snippet, mutation_vars)
end
def mutation_response
@@ -101,7 +102,6 @@ describe 'Updating a Snippet' do
end
it_behaves_like 'graphql update actions'
-
it_behaves_like 'when the snippet is not found'
end
@@ -148,4 +148,40 @@ describe 'Updating a Snippet' do
it_behaves_like 'when the snippet is not found'
end
+
+ context 'when using the files params' do
+ let!(:snippet) { create(:personal_snippet, :private, :repository) }
+ let(:updated_content) { 'updated_content' }
+ let(:updated_file) { 'CHANGELOG' }
+ let(:deleted_file) { 'README' }
+ let(:mutation_vars) do
+ {
+ id: snippet_gid,
+ files: [
+ { action: :update, filePath: updated_file, content: updated_content },
+ { action: :delete, filePath: deleted_file }
+ ]
+ }
+ end
+
+ it 'updates the Snippet' do
+ blob_to_update = blob_at(updated_file)
+ expect(blob_to_update.data).not_to eq updated_content
+
+ blob_to_delete = blob_at(deleted_file)
+ expect(blob_to_delete).to be_present
+
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ blob_to_update = blob_at(updated_file)
+ expect(blob_to_update.data).to eq updated_content
+
+ blob_to_delete = blob_at(deleted_file)
+ expect(blob_to_delete).to be_nil
+ end
+
+ def blob_at(filename)
+ snippet.repository.blob_at('HEAD', filename)
+ end
+ end
end
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 40e085027d7..ed5552f3e30 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Marking all todos done' do
+RSpec.describe 'Marking all todos done' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
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 fabbb3aeb49..9c4733f6769 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Marking todos done' do
+RSpec.describe 'Marking todos done' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/todos/restore_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
index faa36c8273a..6dedde56e13 100644
--- a/spec/requests/api/graphql/mutations/todos/restore_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Restoring Todos' do
+RSpec.describe 'Restoring Todos' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 2a95b99572f..0b634e6b689 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting projects' do
+RSpec.describe 'getting projects' do
include GraphqlHelpers
let(:group) { create(:group) }
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 f7e28043930..44e68c59248 100644
--- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
+++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rendering namespace statistics' do
+RSpec.describe 'rendering namespace statistics' do
include GraphqlHelpers
let(:namespace) { user.namespace }
diff --git a/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb
index 4c048caaeee..dd001a73349 100644
--- a/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting Alert Management Alert Assignees' do
+RSpec.describe 'getting Alert Management Alert Assignees' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb
new file mode 100644
index 00000000000..352a94cfc1d
--- /dev/null
+++ b/spec/requests/api/graphql/project/alert_management/alert/metrics_dashboard_url_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting Alert Management Alert Assignees' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ iid
+ metricsDashboardUrl
+ }
+ QUERY
+ end
+
+ let(:graphql_query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('alertManagementAlerts', {}, fields)
+ )
+ end
+
+ let(:alerts) { graphql_data.dig('project', 'alertManagementAlerts', 'nodes') }
+ let(:first_alert) { alerts.first }
+
+ before do
+ project.add_developer(current_user)
+ end
+
+ context 'with self-managed prometheus payload' do
+ include_context 'self-managed prometheus alert attributes'
+
+ before do
+ create(:alert_management_alert, :prometheus, project: project, payload: payload)
+ end
+
+ it 'includes the correct metrics dashboard url' do
+ post_graphql(graphql_query, current_user: current_user)
+
+ expect(first_alert).to include('metricsDashboardUrl' => dashboard_url_for_alert)
+ end
+ end
+
+ context 'with gitlab-managed prometheus payload' do
+ include_context 'gitlab-managed prometheus alert attributes'
+
+ before do
+ create(:alert_management_alert, :prometheus, project: project, payload: payload, prometheus_alert: prometheus_alert)
+ end
+
+ it 'includes the correct metrics dashboard url' do
+ post_graphql(graphql_query, current_user: current_user)
+
+ expect(first_alert).to include('metricsDashboardUrl' => dashboard_url_for_alert)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
index df6bfa8c97b..1350cba119b 100644
--- a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
@@ -2,15 +2,15 @@
require 'spec_helper'
-describe 'getting Alert Management Alert Notes' do
+RSpec.describe 'getting Alert Management Alert Notes' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let_it_be(:current_user) { create(:user) }
let_it_be(:first_alert) { create(:alert_management_alert, project: project, assignees: [current_user]) }
let_it_be(:second_alert) { create(:alert_management_alert, project: project) }
- let_it_be(:first_system_note) { create(:note_on_alert, noteable: first_alert, project: project) }
- let_it_be(:second_system_note) { create(:note_on_alert, noteable: first_alert, project: project) }
+ let_it_be(:first_system_note) { create(:note_on_alert, :with_system_note_metadata, noteable: first_alert, project: project) }
+ let_it_be(:second_system_note) { create(:note_on_alert, :with_system_note_metadata, noteable: first_alert, project: project) }
let(:params) { {} }
@@ -21,6 +21,8 @@ describe 'getting Alert Management Alert Notes' do
notes {
nodes {
id
+ body
+ systemNoteIconName
}
}
}
@@ -44,7 +46,17 @@ describe 'getting Alert Management Alert Notes' do
project.add_developer(current_user)
end
- it 'returns the notes ordered by createdAt' do
+ it 'includes expected data' do
+ post_graphql(query, current_user: current_user)
+
+ expect(first_notes_result.first).to include(
+ 'id' => first_system_note.to_global_id.to_s,
+ 'systemNoteIconName' => 'git-merge',
+ 'body' => first_system_note.note
+ )
+ end
+
+ it 'returns the notes ordered by createdAt with sufficient content' do
post_graphql(query, current_user: current_user)
expect(first_notes_result.length).to eq(2)
@@ -64,4 +76,18 @@ describe 'getting Alert Management Alert Notes' do
expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(base_count)
expect(alerts_result.length).to eq(3)
end
+
+ context 'for non-system notes' do
+ let_it_be(:user_note) { create(:note_on_alert, noteable: second_alert, project: project) }
+
+ it 'includes expected data' do
+ post_graphql(query, current_user: current_user)
+
+ expect(second_notes_result.first).to include(
+ 'id' => user_note.to_global_id.to_s,
+ 'systemNoteIconName' => nil,
+ 'body' => user_note.note
+ )
+ end
+ end
end
diff --git a/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb b/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
index a0d1ff7efc5..b62215f43fb 100644
--- a/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting Alert Management Alert counts by status' do
+RSpec.describe 'getting Alert Management Alert counts by status' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
index c591895f295..f050c6873f3 100644
--- a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting Alert Management Alerts' do
+RSpec.describe 'getting Alert Management Alerts' do
include GraphqlHelpers
let_it_be(:payload) { { 'custom' => { 'alert' => 'payload' } } }
@@ -73,12 +73,13 @@ describe 'getting Alert Management Alerts' do
'endedAt' => nil,
'details' => { 'custom.alert' => 'payload' },
'createdAt' => triggered_alert.created_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
- 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ')
+ 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'metricsDashboardUrl' => nil
)
expect(second_alert).to include(
'iid' => resolved_alert.iid.to_s,
- 'issueIid' => nil,
+ 'issueIid' => resolved_alert.issue_iid.to_s,
'status' => 'RESOLVED',
'endedAt' => resolved_alert.ended_at.strftime('%Y-%m-%dT%H:%M:%SZ')
)
@@ -109,14 +110,14 @@ describe 'getting Alert Management Alerts' do
it_behaves_like 'a working graphql query'
it 'sorts in the correct order' do
- expect(iids).to eq [resolved_alert.iid.to_s, triggered_alert.iid.to_s]
+ expect(iids).to eq [triggered_alert.iid.to_s, resolved_alert.iid.to_s]
end
context 'ascending order' do
let(:params) { 'sort: SEVERITY_ASC' }
it 'sorts in the correct order' do
- expect(iids).to eq [triggered_alert.iid.to_s, resolved_alert.iid.to_s]
+ expect(iids).to eq [resolved_alert.iid.to_s, triggered_alert.iid.to_s]
end
end
end
diff --git a/spec/requests/api/graphql/project/base_service_spec.rb b/spec/requests/api/graphql/project/base_service_spec.rb
index 8199f331fbf..4dfc242da80 100644
--- a/spec/requests/api/graphql/project/base_service_spec.rb
+++ b/spec/requests/api/graphql/project/base_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira service' do
+RSpec.describe 'query Jira service' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/container_expiration_policy_spec.rb b/spec/requests/api/graphql/project/container_expiration_policy_spec.rb
index d0563f9ff05..b064e4d43e9 100644
--- a/spec/requests/api/graphql/project/container_expiration_policy_spec.rb
+++ b/spec/requests/api/graphql/project/container_expiration_policy_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a repository in a project' do
+RSpec.describe 'getting a repository in a project' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
index a1f9fa1f10c..b2b42137acf 100644
--- a/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
+++ b/spec/requests/api/graphql/project/error_tracking/sentry_detailed_error_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a detailed sentry error' do
+RSpec.describe 'getting a detailed sentry error' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
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 06a0bfc0d32..cd84ce9cb96 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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'sentry errors requests' do
+RSpec.describe 'sentry errors requests' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project_setting) { create(:project_error_tracking_setting, project: project) }
diff --git a/spec/requests/api/graphql/project/grafana_integration_spec.rb b/spec/requests/api/graphql/project/grafana_integration_spec.rb
index c9bc6c1a68e..688959e622d 100644
--- a/spec/requests/api/graphql/project/grafana_integration_spec.rb
+++ b/spec/requests/api/graphql/project/grafana_integration_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'Getting Grafana Integration' do
+RSpec.describe 'Getting Grafana Integration' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
index 04f445b4318..1b654e660e3 100644
--- a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
+++ b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)' do
+RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)' do
include GraphqlHelpers
include DesignManagementTestHelpers
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 18787bf925d..640ac95cd86 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
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting versions related to an issue' do
+RSpec.describe 'Getting versions related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/designs/designs_spec.rb b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
index b6fd0d91bda..e47c025f8b2 100644
--- a/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
+++ b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting designs related to an issue' do
+RSpec.describe 'Getting designs related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
index 0207bb9123a..ae5c8363d0f 100644
--- a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
+++ b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Getting designs related to an issue' do
+RSpec.describe 'Getting designs related to an issue' do
include GraphqlHelpers
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/project/issue/notes_spec.rb b/spec/requests/api/graphql/project/issue/notes_spec.rb
index bfc89434370..97f5261ef1d 100644
--- a/spec/requests/api/graphql/project/issue/notes_spec.rb
+++ b/spec/requests/api/graphql/project/issue/notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting notes for an issue' do
+RSpec.describe 'getting notes for an issue' do
include GraphqlHelpers
let(:noteable) { create(:issue) }
diff --git a/spec/requests/api/graphql/project/issue_spec.rb b/spec/requests/api/graphql/project/issue_spec.rb
index 92d2f9d0d31..5f368833181 100644
--- a/spec/requests/api/graphql/project/issue_spec.rb
+++ b/spec/requests/api/graphql/project/issue_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query.project(fullPath).issue(iid)' do
+RSpec.describe 'Query.project(fullPath).issue(iid)' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 3128f527356..cdfff2f50d4 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting an issue list for a project' do
+RSpec.describe 'getting an issue list for a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
diff --git a/spec/requests/api/graphql/project/jira_import_spec.rb b/spec/requests/api/graphql/project/jira_import_spec.rb
index 7be14696963..814965262b6 100644
--- a/spec/requests/api/graphql/project/jira_import_spec.rb
+++ b/spec/requests/api/graphql/project/jira_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira import data' do
+RSpec.describe 'query Jira import data' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/jira_projects_spec.rb b/spec/requests/api/graphql/project/jira_projects_spec.rb
index d67c89f18c9..d5f59711ab1 100644
--- a/spec/requests/api/graphql/project/jira_projects_spec.rb
+++ b/spec/requests/api/graphql/project/jira_projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira projects' do
+RSpec.describe 'query Jira projects' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -80,34 +80,6 @@ describe 'query Jira projects' do
it_behaves_like 'fetches first project'
end
-
- context 'with before cursor' do
- let(:projects_query) { 'projects(before: "Mg==", first: 1)' }
-
- it_behaves_like 'fetches first project'
- end
-
- context 'with after cursor' do
- let(:projects_query) { 'projects(after: "MA==", first: 1)' }
-
- it_behaves_like 'fetches first project'
- end
- end
-
- context 'with valid but inexistent after cursor' do
- let(:projects_query) { 'projects(after: "MTk==")' }
-
- it 'retuns empty list of jira projects' do
- expect(jira_projects.size).to eq(0)
- end
- end
-
- context 'with invalid after cursor' do
- let(:projects_query) { 'projects(after: "invalid==")' }
-
- it 'treats the invalid cursor as no cursor and returns list of jira projects' do
- expect(jira_projects.size).to eq(2)
- end
end
end
end
diff --git a/spec/requests/api/graphql/project/jira_service_spec.rb b/spec/requests/api/graphql/project/jira_service_spec.rb
index 4ac598b789f..905a669bf0d 100644
--- a/spec/requests/api/graphql/project/jira_service_spec.rb
+++ b/spec/requests/api/graphql/project/jira_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query Jira service' do
+RSpec.describe 'query Jira service' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/labels_query_spec.rb b/spec/requests/api/graphql/project/labels_query_spec.rb
index ecc43e0a3db..eeaaaaee575 100644
--- a/spec/requests/api/graphql/project/labels_query_spec.rb
+++ b/spec/requests/api/graphql/project/labels_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project label information' do
+RSpec.describe 'getting project label information' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :public) }
diff --git a/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb
index c616310a72c..dd16b052e0e 100644
--- a/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request/diff_notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting notes for a merge request' do
+RSpec.describe 'getting notes for a merge request' do
include GraphqlHelpers
let_it_be(:noteable) { create(:merge_request) }
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index 643532bf2e2..c39358a2db1 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting merge request information nested in a project' do
+RSpec.describe 'getting merge request information nested in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
@@ -43,6 +43,59 @@ describe 'getting merge request information nested in a project' do
expect(merge_request_graphql_data['author']['username']).to eq(merge_request.author.username)
end
+ it 'includes diff stats' do
+ be_natural = an_instance_of(Integer).and(be >= 0)
+
+ post_graphql(query, current_user: current_user)
+
+ sums = merge_request_graphql_data['diffStats'].reduce([0, 0, 0]) do |(a, d, c), node|
+ a_, d_ = node.values_at('additions', 'deletions')
+ [a + a_, d + d_, c + a_ + d_]
+ end
+
+ expect(merge_request_graphql_data).to include(
+ 'diffStats' => all(a_hash_including('path' => String, 'additions' => be_natural, 'deletions' => be_natural)),
+ 'diffStatsSummary' => a_hash_including(
+ 'fileCount' => merge_request.diff_stats.count,
+ 'additions' => be_natural,
+ 'deletions' => be_natural,
+ 'changes' => be_natural
+ )
+ )
+
+ # diff_stats is consistent with summary
+ expect(merge_request_graphql_data['diffStatsSummary']
+ .values_at('additions', 'deletions', 'changes')).to eq(sums)
+
+ # diff_stats_summary is internally consistent
+ expect(merge_request_graphql_data['diffStatsSummary']
+ .values_at('additions', 'deletions').sum)
+ .to eq(merge_request_graphql_data.dig('diffStatsSummary', 'changes'))
+ .and be_positive
+ end
+
+ context 'requesting a specific diff stat' do
+ let(:diff_stat) { merge_request.diff_stats.first }
+
+ let(:query) do
+ graphql_query_for(:project, { full_path: project.full_path },
+ query_graphql_field(:merge_request, { iid: merge_request.iid.to_s }, [
+ query_graphql_field(:diff_stats, { path: diff_stat.path }, all_graphql_fields_for('DiffStats'))
+ ])
+ )
+ end
+
+ it 'includes only the requested stats' do
+ post_graphql(query, current_user: current_user)
+
+ expect(merge_request_graphql_data).to include(
+ 'diffStats' => contain_exactly(
+ a_hash_including('path' => diff_stat.path, 'additions' => diff_stat.additions, 'deletions' => diff_stat.deletions)
+ )
+ )
+ end
+ end
+
it 'includes correct mergedAt value when merged' do
time = 1.week.ago
merge_request.mark_as_merged
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 49fdfe29874..e2255fdb048 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting merge request listings nested in a project' do
+RSpec.describe 'getting merge request listings nested in a project' do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository, :public) }
diff --git a/spec/requests/api/graphql/project/packages_spec.rb b/spec/requests/api/graphql/project/packages_spec.rb
new file mode 100644
index 00000000000..88f97f9256b
--- /dev/null
+++ b/spec/requests/api/graphql/project/packages_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting a package list for a project' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:package) { create(:package, project: project) }
+ let(:packages_data) { graphql_data['project']['packages']['edges'] }
+
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ #{all_graphql_fields_for('packages'.classify)}
+ }
+ }
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('packages', {}, fields)
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'when user has access to the project' do
+ before do
+ project.add_reporter(current_user)
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns packages successfully' do
+ expect(packages_data[0]['node']['name']).to eq package.name
+ end
+ end
+
+ context 'when the user does not have access to the project/packages' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns nil' do
+ expect(graphql_data['project']).to be_nil
+ end
+ end
+
+ context 'when the user is not autenthicated' do
+ before do
+ post_graphql(query)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns nil' do
+ expect(graphql_data['project']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index bed9a18577f..57b9de25c3d 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting pipeline information nested in a project' do
+RSpec.describe 'getting pipeline information nested in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb
index 05dd5d36c26..c226b10ab51 100644
--- a/spec/requests/api/graphql/project/project_statistics_spec.rb
+++ b/spec/requests/api/graphql/project/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rendering project statistics' do
+RSpec.describe 'rendering project statistics' do
include GraphqlHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index f8624a97a2b..f9c19d9747d 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -1,206 +1,374 @@
# frozen_string_literal: true
require 'spec_helper'
-require 'pp'
-describe 'Query.project(fullPath).release(tagName)' do
+RSpec.describe 'Query.project(fullPath).release(tagName)' do
include GraphqlHelpers
include Presentable
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:milestone_1) { create(:milestone, project: project) }
- let_it_be(:milestone_2) { create(:milestone, project: project) }
- let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
- let_it_be(:release_link_1) { create(:release_link, release: release) }
- let_it_be(:release_link_2) { create(:release_link, release: release) }
let_it_be(:developer) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:stranger) { create(:user) }
- let(:current_user) { developer }
+ let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } }
+ let(:post_query) { post_graphql(query, current_user: current_user) }
+ let(:path_prefix) { %w[project release] }
+ let(:data) { graphql_data.dig(*path) }
def query(rq = release_fields)
graphql_query_for(:project, { fullPath: project.full_path },
query_graphql_field(:release, { tagName: release.tag }, rq))
end
- let(:post_query) { post_graphql(query, current_user: current_user) }
- let(:path_prefix) { %w[project release] }
-
- let(:data) { graphql_data.dig(*path) }
-
before do
- project.add_developer(developer)
+ stub_default_url_options(host: 'www.example.com')
end
- describe 'scalar fields' do
- let(:path) { path_prefix }
- let(:release_fields) do
- query_graphql_field(%{
- tagName
- tagPath
- description
- descriptionHtml
- name
- createdAt
- releasedAt
- })
+ shared_examples 'full access to the release field' do
+ describe 'scalar fields' do
+ let(:path) { path_prefix }
+
+ let(:release_fields) do
+ query_graphql_field(%{
+ tagName
+ tagPath
+ description
+ descriptionHtml
+ name
+ createdAt
+ releasedAt
+ })
+ end
+
+ before do
+ post_query
+ end
+
+ it 'finds all release data' do
+ expect(data).to eq({
+ 'tagName' => release.tag,
+ 'tagPath' => project_tag_path(project, release.tag),
+ 'description' => release.description,
+ 'descriptionHtml' => release.description_html,
+ 'name' => release.name,
+ 'createdAt' => release.created_at.iso8601,
+ 'releasedAt' => release.released_at.iso8601
+ })
+ end
end
- before do
- post_query
+ describe 'milestones' do
+ let(:path) { path_prefix + %w[milestones nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:milestones, nil, 'nodes { id title }')
+ end
+
+ it 'finds all milestones associated to a release' do
+ post_query
+
+ expected = release.milestones.map do |milestone|
+ { 'id' => global_id_of(milestone), 'title' => milestone.title }
+ end
+
+ expect(data).to match_array(expected)
+ end
end
- it 'finds all release data' do
- expect(data).to eq({
- 'tagName' => release.tag,
- 'tagPath' => project_tag_path(project, release.tag),
- 'description' => release.description,
- 'descriptionHtml' => release.description_html,
- 'name' => release.name,
- 'createdAt' => release.created_at.iso8601,
- 'releasedAt' => release.released_at.iso8601
- })
+ describe 'author' do
+ let(:path) { path_prefix + %w[author] }
+
+ let(:release_fields) do
+ query_graphql_field(:author, nil, 'id username')
+ end
+
+ it 'finds the author of the release' do
+ post_query
+
+ expect(data).to eq(
+ 'id' => global_id_of(release.author),
+ 'username' => release.author.username
+ )
+ end
end
- end
- describe 'milestones' do
- let(:path) { path_prefix + %w[milestones nodes] }
- let(:release_fields) do
- query_graphql_field(:milestones, nil, 'nodes { id title }')
+ describe 'commit' do
+ let(:path) { path_prefix + %w[commit] }
+
+ let(:release_fields) do
+ query_graphql_field(:commit, nil, 'sha')
+ end
+
+ it 'finds the commit associated with the release' do
+ post_query
+
+ expect(data).to eq('sha' => release.commit.sha)
+ end
end
- it 'finds all milestones associated to a release' do
- post_query
+ describe 'assets' do
+ describe 'count' do
+ let(:path) { path_prefix + %w[assets] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil, 'count')
+ end
+
+ it 'returns the number of assets associated to the release' do
+ post_query
+
+ expect(data).to eq('count' => release.sources.size + release.links.size)
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[assets links nodes] }
- expected = release.milestones.map do |milestone|
- { 'id' => global_id_of(milestone), 'title' => milestone.title }
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:links, nil, 'nodes { id name url external }'))
+ end
+
+ it 'finds all release links' do
+ post_query
+
+ expected = release.links.map do |link|
+ {
+ 'id' => global_id_of(link),
+ 'name' => link.name,
+ 'url' => link.url,
+ 'external' => link.external?
+ }
+ end
+
+ expect(data).to match_array(expected)
+ end
end
- expect(data).to match_array(expected)
+ describe 'sources' do
+ let(:path) { path_prefix + %w[assets sources nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:sources, nil, 'nodes { format url }'))
+ end
+
+ it 'finds all release sources' do
+ post_query
+
+ expected = release.sources.map do |source|
+ {
+ 'format' => source.format,
+ 'url' => source.url
+ }
+ end
+
+ expect(data).to match_array(expected)
+ end
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[links] }
+
+ let(:release_fields) do
+ query_graphql_field(:links, nil, %{
+ selfUrl
+ mergeRequestsUrl
+ issuesUrl
+ })
+ end
+
+ it 'finds all release links' do
+ post_query
+
+ expect(data).to eq(
+ 'selfUrl' => project_release_url(project, release),
+ 'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs),
+ 'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs)
+ )
+ end
+ end
+
+ describe 'evidences' do
+ let(:path) { path_prefix + %w[evidences] }
+
+ let(:release_fields) do
+ query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
+ end
+
+ it 'finds all evidence fields' do
+ post_query
+
+ evidence = release.evidences.first.present
+
+ expect(data["nodes"].first).to eq(
+ 'id' => global_id_of(evidence),
+ 'sha' => evidence.sha,
+ 'filepath' => evidence.filepath,
+ 'collectedAt' => evidence.collected_at.utc.iso8601
+ )
+ end
+ end
+ end
+
+ shared_examples 'no access to the release field' do
+ describe 'repository-related fields' do
+ let(:path) { path_prefix }
+
+ let(:release_fields) do
+ query_graphql_field('description')
+ end
+
+ before do
+ post_query
+ end
+
+ it 'returns nil' do
+ expect(data).to eq(nil)
+ end
end
end
- describe 'author' do
- let(:path) { path_prefix + %w[author] }
+ shared_examples 'access to editUrl' do
+ let(:path) { path_prefix + %w[links] }
+
let(:release_fields) do
- query_graphql_field(:author, nil, 'id username')
+ query_graphql_field(:links, nil, 'editUrl')
end
- it 'finds the author of the release' do
+ before do
post_query
+ end
- expect(data).to eq({
- 'id' => global_id_of(release.author),
- 'username' => release.author.username
- })
+ it 'returns editUrl' do
+ expect(data).to eq('editUrl' => edit_project_release_url(project, release))
end
end
- describe 'commit' do
- let(:path) { path_prefix + %w[commit] }
+ shared_examples 'no access to editUrl' do
+ let(:path) { path_prefix + %w[links] }
+
let(:release_fields) do
- query_graphql_field(:commit, nil, 'sha')
+ query_graphql_field(:links, nil, 'editUrl')
end
- it 'finds the commit associated with the release' do
+ before do
post_query
+ end
- expect(data).to eq({ 'sha' => release.commit.sha })
+ it 'does not return editUrl' do
+ expect(data).to eq('editUrl' => nil)
end
end
- describe 'assets' do
- describe 'assetsCount' do
- let(:path) { path_prefix + %w[assets] }
- let(:release_fields) do
- query_graphql_field(:assets, nil, 'assetsCount')
+ describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do
+ context 'when the project is private' do
+ let_it_be(:project) { create(:project, :repository, :private) }
+ let_it_be(:milestone_1) { create(:milestone, project: project) }
+ let_it_be(:milestone_2) { create(:milestone, project: project) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
+ let_it_be(:release_link_1) { create(:release_link, release: release) }
+ let_it_be(:release_link_2) { create(:release_link, release: release) }
+
+ before_all do
+ project.add_developer(developer)
+ project.add_guest(guest)
+ project.add_reporter(reporter)
end
- it 'returns the number of assets associated to the release' do
- post_query
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
- expect(data).to eq({ 'assetsCount' => release.sources.size + release.links.size })
+ it_behaves_like 'no access to the release field'
end
- end
- describe 'links' do
- let(:path) { path_prefix + %w[assets links nodes] }
- let(:release_fields) do
- query_graphql_field(:assets, nil,
- query_graphql_field(:links, nil, 'nodes { id name url external }'))
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'no access to the release field'
end
- it 'finds all release links' do
- post_query
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
- expected = release.links.map do |link|
- {
- 'id' => global_id_of(link),
- 'name' => link.name,
- 'url' => link.url,
- 'external' => link.external?
- }
- end
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
+ end
- expect(data).to match_array(expected)
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'access to editUrl'
end
end
- describe 'sources' do
- let(:path) { path_prefix + %w[assets sources nodes] }
- let(:release_fields) do
- query_graphql_field(:assets, nil,
- query_graphql_field(:sources, nil, 'nodes { format url }'))
+ context 'when the project is public' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:milestone_1) { create(:milestone, project: project) }
+ let_it_be(:milestone_2) { create(:milestone, project: project) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
+ let_it_be(:release_link_1) { create(:release_link, release: release) }
+ let_it_be(:release_link_2) { create(:release_link, release: release) }
+
+ before_all do
+ project.add_developer(developer)
+ project.add_guest(guest)
+ project.add_reporter(reporter)
end
- it 'finds all release sources' do
- post_query
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
- expected = release.sources.map do |source|
- {
- 'format' => source.format,
- 'url' => source.url
- }
- end
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
+ end
- expect(data).to match_array(expected)
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
end
- end
- describe 'evidences' do
- let(:path) { path_prefix + %w[evidences] }
- let(:release_fields) do
- query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'no access to editUrl'
end
- context 'for a developer' do
- it 'finds all evidence fields' do
- post_query
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
- evidence = release.evidences.first.present
- expected = {
- 'id' => global_id_of(evidence),
- 'sha' => evidence.sha,
- 'filepath' => evidence.filepath,
- 'collectedAt' => evidence.collected_at.utc.iso8601
- }
+ it_behaves_like 'full access to the release field'
+ end
- expect(data["nodes"].first).to eq(expected)
- end
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to the release field'
+ it_behaves_like 'access to editUrl'
end
+ end
+ end
- context 'for a guest' do
- let(:current_user) { create :user }
+ describe 'ensures that the release data can be contolled by a feature flag' do
+ context 'when the graphql_release_data feature flag is disabled' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:release) { create(:release, project: project) }
- before do
- project.add_guest(current_user)
- end
+ let(:current_user) { developer }
- it 'denies access' do
- post_query
+ before do
+ stub_feature_flags(graphql_release_data: false)
- expect(data['node']).to be_nil
- end
+ project.add_developer(developer)
end
+
+ it_behaves_like 'no access to the release field'
end
end
end
diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb
new file mode 100644
index 00000000000..7e418bbaa5b
--- /dev/null
+++ b/spec/requests/api/graphql/project/releases_spec.rb
@@ -0,0 +1,284 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.project(fullPath).releases()' do
+ include GraphqlHelpers
+
+ let_it_be(:stranger) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ releases {
+ nodes {
+ tagName
+ tagPath
+ name
+ commit {
+ sha
+ }
+ assets {
+ count
+ sources {
+ nodes {
+ url
+ }
+ }
+ }
+ evidences {
+ nodes {
+ sha
+ }
+ }
+ links {
+ selfUrl
+ mergeRequestsUrl
+ issuesUrl
+ }
+ }
+ }
+ })
+ end
+
+ let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } }
+ let(:post_query) { post_graphql(query, current_user: current_user) }
+
+ let(:data) { graphql_data.dig('project', 'releases', 'nodes', 0) }
+
+ before do
+ stub_default_url_options(host: 'www.example.com')
+ end
+
+ shared_examples 'full access to all repository-related fields' do
+ describe 'repository-related fields' do
+ before do
+ post_query
+ end
+
+ it 'returns data for fields that are protected in private projects' do
+ expected_sources = release.sources.map do |s|
+ { 'url' => s.url }
+ end
+
+ expected_evidences = release.evidences.map do |e|
+ { 'sha' => e.sha }
+ end
+
+ expect(data).to eq(
+ 'tagName' => release.tag,
+ 'tagPath' => project_tag_path(project, release.tag),
+ 'name' => release.name,
+ 'commit' => {
+ 'sha' => release.commit.sha
+ },
+ 'assets' => {
+ 'count' => release.assets_count,
+ 'sources' => {
+ 'nodes' => expected_sources
+ }
+ },
+ 'evidences' => {
+ 'nodes' => expected_evidences
+ },
+ 'links' => {
+ 'selfUrl' => project_release_url(project, release),
+ 'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs),
+ 'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs)
+ }
+ )
+ end
+ end
+ end
+
+ shared_examples 'no access to any repository-related fields' do
+ describe 'repository-related fields' do
+ before do
+ post_query
+ end
+
+ it 'does not return data for fields that expose repository information' do
+ expect(data).to eq(
+ 'tagName' => nil,
+ 'tagPath' => nil,
+ 'name' => "Release-#{release.id}",
+ 'commit' => nil,
+ 'assets' => {
+ 'count' => release.assets_count(except: [:sources]),
+ 'sources' => {
+ 'nodes' => []
+ }
+ },
+ 'evidences' => {
+ 'nodes' => []
+ },
+ 'links' => nil
+ )
+ end
+ end
+ end
+
+ # editUrl is tested separately becuase its permissions
+ # are slightly different than other release fields
+ shared_examples 'access to editUrl' do
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ releases {
+ nodes {
+ links {
+ editUrl
+ }
+ }
+ }
+ })
+ end
+
+ before do
+ post_query
+ end
+
+ it 'returns editUrl' do
+ expect(data).to eq(
+ 'links' => {
+ 'editUrl' => edit_project_release_url(project, release)
+ }
+ )
+ end
+ end
+
+ shared_examples 'no access to editUrl' do
+ let(:query) do
+ graphql_query_for(:project, { fullPath: project.full_path },
+ %{
+ releases {
+ nodes {
+ links {
+ editUrl
+ }
+ }
+ }
+ })
+ end
+
+ before do
+ post_query
+ end
+
+ it 'does not return editUrl' do
+ expect(data).to eq(
+ 'links' => {
+ 'editUrl' => nil
+ }
+ )
+ end
+ end
+
+ shared_examples 'no access to any release data' do
+ before do
+ post_query
+ end
+
+ it 'returns nil' do
+ expect(data).to eq(nil)
+ end
+ end
+
+ describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do
+ context 'when the project is private' do
+ let_it_be(:project) { create(:project, :repository, :private) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project) }
+
+ before_all do
+ project.add_guest(guest)
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ end
+
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
+
+ it_behaves_like 'no access to any release data'
+ end
+
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'no access to any repository-related fields'
+ end
+
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'access to editUrl'
+ end
+ end
+
+ context 'when the project is public' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project) }
+
+ before_all do
+ project.add_guest(guest)
+ project.add_reporter(reporter)
+ project.add_developer(developer)
+ end
+
+ context 'when the user is not logged in' do
+ let(:current_user) { stranger }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Guest permissions' do
+ let(:current_user) { guest }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Reporter permissions' do
+ let(:current_user) { reporter }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'no access to editUrl'
+ end
+
+ context 'when the user has Developer permissions' do
+ let(:current_user) { developer }
+
+ it_behaves_like 'full access to all repository-related fields'
+ it_behaves_like 'access to editUrl'
+ end
+ end
+ end
+
+ describe 'ensures that the release data can be contolled by a feature flag' do
+ context 'when the graphql_release_data feature flag is disabled' do
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:release) { create(:release, project: project) }
+
+ let(:current_user) { developer }
+
+ before do
+ stub_feature_flags(graphql_release_data: false)
+
+ project.add_developer(developer)
+ end
+
+ it_behaves_like 'no access to any release data'
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/repository_spec.rb b/spec/requests/api/graphql/project/repository_spec.rb
index 261433a3d6a..bd719a69647 100644
--- a/spec/requests/api/graphql/project/repository_spec.rb
+++ b/spec/requests/api/graphql/project/repository_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a repository in a project' do
+RSpec.describe 'getting a repository in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb
index 94128cc21ee..bce63d57c38 100644
--- a/spec/requests/api/graphql/project/tree/tree_spec.rb
+++ b/spec/requests/api/graphql/project/tree/tree_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'getting a tree in a project' do
+RSpec.describe 'getting a tree in a project' do
include GraphqlHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb
index 9a88b47eea6..b115030afbc 100644
--- a/spec/requests/api/graphql/project_query_spec.rb
+++ b/spec/requests/api/graphql/project_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting project information' do
+RSpec.describe 'getting project information' do
include GraphqlHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/query_spec.rb b/spec/requests/api/graphql/query_spec.rb
index 26b4c6eafd7..6bd0703c121 100644
--- a/spec/requests/api/graphql/query_spec.rb
+++ b/spec/requests/api/graphql/query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Query' do
+RSpec.describe 'Query' do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
diff --git a/spec/requests/api/graphql/read_only_spec.rb b/spec/requests/api/graphql/read_only_spec.rb
index 1d28a71258d..ce8a3f6ef5c 100644
--- a/spec/requests/api/graphql/read_only_spec.rb
+++ b/spec/requests/api/graphql/read_only_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Requests on a read-only node' do
+RSpec.describe 'Requests on a read-only node' do
include GraphqlHelpers
before do
diff --git a/spec/requests/api/graphql/tasks/task_completion_status_spec.rb b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb
index c47406ea534..5f4d2aec718 100644
--- a/spec/requests/api/graphql/tasks/task_completion_status_spec.rb
+++ b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting task completion status information' do
+RSpec.describe 'getting task completion status information' do
include GraphqlHelpers
description_0_done = '- [ ] task 1\n- [ ] task 2'
diff --git a/spec/requests/api/graphql/user/group_member_query_spec.rb b/spec/requests/api/graphql/user/group_member_query_spec.rb
index 022ee79297c..3a16d962214 100644
--- a/spec/requests/api/graphql/user/group_member_query_spec.rb
+++ b/spec/requests/api/graphql/user/group_member_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'GroupMember' do
+RSpec.describe 'GroupMember' do
include GraphqlHelpers
let_it_be(:member) { create(:group_member, :developer) }
diff --git a/spec/requests/api/graphql/user/project_member_query_spec.rb b/spec/requests/api/graphql/user/project_member_query_spec.rb
index 397d2872189..0790e148caf 100644
--- a/spec/requests/api/graphql/user/project_member_query_spec.rb
+++ b/spec/requests/api/graphql/user/project_member_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'ProjectMember' do
+RSpec.describe 'ProjectMember' do
include GraphqlHelpers
let_it_be(:member) { create(:project_member, :developer) }
diff --git a/spec/requests/api/graphql/user_query_spec.rb b/spec/requests/api/graphql/user_query_spec.rb
index 5ac94bc7323..7ba1788a9ef 100644
--- a/spec/requests/api/graphql/user_query_spec.rb
+++ b/spec/requests/api/graphql/user_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting user information' do
+RSpec.describe 'getting user information' do
include GraphqlHelpers
let(:query) do
diff --git a/spec/requests/api/graphql/user_spec.rb b/spec/requests/api/graphql/user_spec.rb
index 097c75b3541..d2d6b1fca66 100644
--- a/spec/requests/api/graphql/user_spec.rb
+++ b/spec/requests/api/graphql/user_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'User' do
+RSpec.describe 'User' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/users_spec.rb b/spec/requests/api/graphql/users_spec.rb
index 1e6d73cbd7d..91ac206676b 100644
--- a/spec/requests/api/graphql/users_spec.rb
+++ b/spec/requests/api/graphql/users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Users' do
+RSpec.describe 'Users' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user, created_at: 1.day.ago) }
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index 84be5ab0951..ff1a5aa1540 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'GraphQL' do
+RSpec.describe 'GraphQL' do
include GraphqlHelpers
let(:query) { graphql_query_for('echo', 'text' => 'Hello world' ) }
diff --git a/spec/requests/api/group_boards_spec.rb b/spec/requests/api/group_boards_spec.rb
index a9083f82f25..6ce8b766807 100644
--- a/spec/requests/api/group_boards_spec.rb
+++ b/spec/requests/api/group_boards_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupBoards do
+RSpec.describe API::GroupBoards do
let_it_be(:user) { create(:user) }
let_it_be(:non_member) { create(:user) }
let_it_be(:guest) { create(:user) }
diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb
index fade54f6b11..068af1485e2 100644
--- a/spec/requests/api/group_clusters_spec.rb
+++ b/spec/requests/api/group_clusters_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupClusters do
+RSpec.describe API::GroupClusters do
include KubernetesHelpers
let(:current_user) { create(:user) }
@@ -266,29 +266,51 @@ describe API::GroupClusters do
end
end
- context 'when user tries to add multiple clusters' do
+ context 'non-authorized user' do
before do
- create(:cluster, :provided_by_gcp, :group,
- groups: [group])
-
- post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
+ post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
end
- it 'responds with 400' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first).to eq(_('Instance does not support multiple Kubernetes clusters'))
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
+ end
- context 'non-authorized user' do
+ describe 'PUT /groups/:id/clusters/:cluster_id' do
+ let(:api_url) { 'https://kubernetes.example.com' }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token'
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ environment_scope: 'test/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'when another cluster exists' do
before do
- post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
+ create(:cluster, :provided_by_gcp, :group,
+ groups: [group])
+
+ post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
end
- it 'responds with 403' do
- expect(response).to have_gitlab_http_status(:forbidden)
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
- expect(json_response['message']).to eq('403 Forbidden')
+ it 'allows multiple clusters to be associated to group' do
+ expect(group.reload.clusters.count).to eq(2)
end
end
end
diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb
index 9f439bb2167..3128becae6d 100644
--- a/spec/requests/api/group_container_repositories_spec.rb
+++ b/spec/requests/api/group_container_repositories_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupContainerRepositories do
+RSpec.describe API::GroupContainerRepositories do
let_it_be(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, :private, group: group) }
let_it_be(:reporter) { create(:user) }
diff --git a/spec/requests/api/group_export_spec.rb b/spec/requests/api/group_export_spec.rb
index 9dd7797c768..50a1e9d0c3d 100644
--- a/spec/requests/api/group_export_spec.rb
+++ b/spec/requests/api/group_export_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupExport do
+RSpec.describe API::GroupExport do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
@@ -33,6 +33,10 @@ describe API::GroupExport do
context 'group_import_export feature flag enabled' do
before do
stub_feature_flags(group_import_export: true)
+
+ allow(Gitlab::ApplicationRateLimiter)
+ .to receive(:increment)
+ .and_return(0)
end
context 'when export file exists' do
@@ -87,7 +91,7 @@ describe API::GroupExport do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_download_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
@@ -162,7 +166,7 @@ describe API::GroupExport do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:group_export][:threshold].call + 1)
end
it 'throttles the endpoint' do
diff --git a/spec/requests/api/group_import_spec.rb b/spec/requests/api/group_import_spec.rb
index b60a1b3f119..ad67f737725 100644
--- a/spec/requests/api/group_import_spec.rb
+++ b/spec/requests/api/group_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupImport do
+RSpec.describe API::GroupImport do
include WorkhorseHelpers
let_it_be(:user) { create(:user) }
@@ -122,6 +122,7 @@ describe API::GroupImport do
before do
allow_next_instance_of(Group) do |group|
allow(group).to receive(:persisted?).and_return(false)
+ allow(group).to receive(:save).and_return(false)
end
end
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
index 715c1255cb3..f965a845bbe 100644
--- a/spec/requests/api/group_labels_spec.rb
+++ b/spec/requests/api/group_labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupLabels do
+RSpec.describe API::GroupLabels do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb
index 3e9b6246434..2b361f2b503 100644
--- a/spec/requests/api/group_milestones_spec.rb
+++ b/spec/requests/api/group_milestones_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupMilestones do
+RSpec.describe API::GroupMilestones do
let(:user) { create(:user) }
let(:group) { create(:group, :private) }
let(:project) { create(:project, namespace: group) }
diff --git a/spec/requests/api/group_packages_spec.rb b/spec/requests/api/group_packages_spec.rb
new file mode 100644
index 00000000000..7c7e8da3fb1
--- /dev/null
+++ b/spec/requests/api/group_packages_spec.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::GroupPackages do
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project, :public, namespace: group, name: 'project A') }
+ let_it_be(:user) { create(:user) }
+
+ subject { get api(url) }
+
+ describe 'GET /groups/:id/packages' do
+ let(:url) { "/groups/#{group.id}/packages" }
+ let(:package_schema) { 'public_api/v4/packages/group_packages' }
+
+ context 'without the need for a license' do
+ context 'with sorting' do
+ let_it_be(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
+ let_it_be(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
+ let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
+
+ before do
+ travel_to(1.day.ago) do
+ package3
+ end
+ end
+
+ context 'without sorting params' do
+ let(:packages) { [package3, package1, package2] }
+
+ it 'sorts by created_at asc' do
+ subject
+
+ expect(json_response.map { |package| package['id'] }).to eq(packages.map(&:id))
+ end
+ end
+
+ it_behaves_like 'package sorting', 'name' do
+ let(:packages) { [package1, package2, package3] }
+ end
+
+ it_behaves_like 'package sorting', 'created_at' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting', 'version' do
+ let(:packages) { [package3, package2, package1] }
+ end
+
+ it_behaves_like 'package sorting', 'type' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting', 'project_path' do
+ let(:another_project) { create(:project, :public, namespace: group, name: 'project B') }
+ let!(:package4) { create(:npm_package, project: another_project, version: '3.1.0', name: "@#{project.root_namespace.path}/bar") }
+
+ let(:packages) { [package1, package2, package3, package4] }
+ end
+ end
+
+ context 'with private group' do
+ let!(:package1) { create(:package, project: project) }
+ let!(:package2) { create(:package, project: project) }
+
+ let(:group) { create(:group, :private) }
+ let(:subgroup) { create(:group, :private, parent: group) }
+ let(:project) { create(:project, :private, namespace: group) }
+ let(:subproject) { create(:project, :private, namespace: subgroup) }
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects packages access', :group, :no_type, :not_found
+ end
+
+ context 'with authenticated user' do
+ subject { get api(url, user) }
+
+ it_behaves_like 'returns packages', :group, :owner
+ it_behaves_like 'returns packages', :group, :maintainer
+ it_behaves_like 'returns packages', :group, :developer
+ it_behaves_like 'rejects packages access', :group, :reporter, :forbidden
+ it_behaves_like 'rejects packages access', :group, :guest, :forbidden
+
+ context 'with subgroup' do
+ let(:subgroup) { create(:group, :private, parent: group) }
+ let(:subproject) { create(:project, :private, namespace: subgroup) }
+ let!(:package3) { create(:npm_package, project: subproject) }
+
+ it_behaves_like 'returns packages with subgroups', :group, :owner
+ it_behaves_like 'returns packages with subgroups', :group, :maintainer
+ it_behaves_like 'returns packages with subgroups', :group, :developer
+ it_behaves_like 'rejects packages access', :group, :reporter, :forbidden
+ it_behaves_like 'rejects packages access', :group, :guest, :forbidden
+
+ context 'excluding subgroup' do
+ let(:url) { "/groups/#{group.id}/packages?exclude_subgroups=true" }
+
+ it_behaves_like 'returns packages', :group, :owner
+ it_behaves_like 'returns packages', :group, :maintainer
+ it_behaves_like 'returns packages', :group, :developer
+ it_behaves_like 'rejects packages access', :group, :reporter, :forbidden
+ it_behaves_like 'rejects packages access', :group, :guest, :forbidden
+ end
+ end
+ end
+ end
+
+ context 'with public group' do
+ let_it_be(:package1) { create(:package, project: project) }
+ let_it_be(:package2) { create(:package, project: project) }
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'returns packages', :group, :no_type
+ end
+
+ context 'with authenticated user' do
+ subject { get api(url, user) }
+
+ it_behaves_like 'returns packages', :group, :owner
+ it_behaves_like 'returns packages', :group, :maintainer
+ it_behaves_like 'returns packages', :group, :developer
+ it_behaves_like 'returns packages', :group, :reporter
+ it_behaves_like 'returns packages', :group, :guest
+ end
+ end
+
+ context 'with pagination params' do
+ let_it_be(:package1) { create(:package, project: project) }
+ let_it_be(:package2) { create(:package, project: project) }
+ let_it_be(:package3) { create(:npm_package, project: project) }
+ let_it_be(:package4) { create(:npm_package, project: project) }
+
+ it_behaves_like 'returns paginated packages'
+ end
+
+ it_behaves_like 'filters on each package_type', is_project: false
+
+ context 'does not accept non supported package_type value' do
+ include_context 'package filter context'
+
+ let(:url) { group_filter_url(:type, 'foo') }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb
index a5b48985df5..c6d6ae1615b 100644
--- a/spec/requests/api/group_variables_spec.rb
+++ b/spec/requests/api/group_variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::GroupVariables do
+RSpec.describe API::GroupVariables do
let(:group) { create(:group) }
let(:user) { create(:user) }
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 9a449499576..fac9f4dfe00 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Groups do
+RSpec.describe API::Groups do
include GroupAPIHelpers
include UploadHelpers
@@ -15,6 +15,7 @@ describe API::Groups do
let_it_be(:project1) { create(:project, namespace: group1) }
let_it_be(:project2) { create(:project, namespace: group2) }
let_it_be(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
+ let_it_be(:archived_project) { create(:project, namespace: group1, archived: true) }
before do
group1.add_owner(user1)
@@ -184,11 +185,12 @@ describe API::Groups do
it "includes statistics if requested" do
attributes = {
- storage_size: 1158,
+ storage_size: 2392,
repository_size: 123,
wiki_size: 456,
lfs_objects_size: 234,
- build_artifacts_size: 345
+ build_artifacts_size: 345,
+ snippets_size: 1234
}.stringify_keys
exposed_attributes = attributes.dup
exposed_attributes['job_artifacts_size'] = exposed_attributes.delete('build_artifacts_size')
@@ -470,7 +472,7 @@ describe API::Groups do
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]).to have_key('expires_at')
expect(json_response['projects']).to be_an Array
- expect(json_response['projects'].length).to eq(2)
+ expect(json_response['projects'].length).to eq(3)
expect(json_response['shared_projects']).to be_an Array
expect(json_response['shared_projects'].length).to eq(1)
expect(json_response['shared_projects'][0]['id']).to eq(project.id)
@@ -695,7 +697,7 @@ describe API::Groups do
expect(json_response['parent_id']).to eq(nil)
expect(json_response['created_at']).to be_present
expect(json_response['projects']).to be_an Array
- expect(json_response['projects'].length).to eq(2)
+ expect(json_response['projects'].length).to eq(3)
expect(json_response['shared_projects']).to be_an Array
expect(json_response['shared_projects'].length).to eq(0)
expect(json_response['default_branch_protection']).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
@@ -821,20 +823,51 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
project_names = json_response.map { |proj| proj['name'] }
- expect(project_names).to match_array([project1.name, project3.name])
+ expect(project_names).to match_array([project1.name, project3.name, archived_project.name])
expect(json_response.first['visibility']).to be_present
end
+ context 'and using archived' do
+ it "returns the group's archived projects" do
+ get api("/groups/#{group1.id}/projects?archived=true", user1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(Project.public_or_visible_to_user(user1).where(archived: true).size)
+ expect(json_response.map { |project| project['id'] }).to include(archived_project.id)
+ end
+
+ it "returns the group's non-archived projects" do
+ get api("/groups/#{group1.id}/projects?archived=false", user1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(Project.public_or_visible_to_user(user1).where(archived: false).size)
+ expect(json_response.map { |project| project['id'] }).not_to include(archived_project.id)
+ end
+
+ it "returns all of the group's projects" do
+ get api("/groups/#{group1.id}/projects", user1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(*Project.public_or_visible_to_user(user1).pluck(:id))
+ end
+ end
+
it "returns the group's projects with simple representation" do
get api("/groups/#{group1.id}/projects", user1), params: { simple: true }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
project_names = json_response.map { |proj| proj['name'] }
- expect(project_names).to match_array([project1.name, project3.name])
+ expect(project_names).to match_array([project1.name, project3.name, archived_project.name])
expect(json_response.first['visibility']).not_to be_present
end
@@ -860,7 +893,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(2)
+ expect(json_response.length).to eq(3)
end
it "returns projects including those in subgroups" do
@@ -873,7 +906,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(4)
+ expect(json_response.length).to eq(5)
end
it "does not return a non existing group" do
@@ -958,7 +991,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
project_names = json_response.map { |proj| proj['name'] }
- expect(project_names).to match_array([project1.name, project3.name])
+ expect(project_names).to match_array([project1.name, project3.name, archived_project.name])
end
it 'does not return a non existing group' do
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index d65c89f48ea..12cd5ace84e 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
require 'raven/transports/dummy'
require_relative '../../../config/initializers/sentry'
-describe API::Helpers do
+RSpec.describe API::Helpers do
include API::APIGuard::HelperMethods
include described_class
include TermsHelper
diff --git a/spec/requests/api/import_bitbucket_server_spec.rb b/spec/requests/api/import_bitbucket_server_spec.rb
new file mode 100644
index 00000000000..5828dab3080
--- /dev/null
+++ b/spec/requests/api/import_bitbucket_server_spec.rb
@@ -0,0 +1,218 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::ImportBitbucketServer do
+ let(:base_uri) { "https://test:7990" }
+ let(:user) { create(:user) }
+ let(:token) { "asdasd12345" }
+ let(:secret) { "sekrettt" }
+ let(:project_key) { 'TES' }
+ let(:repo_slug) { 'vim' }
+ let(:repo) { { name: 'vim' } }
+
+ describe "POST /import/bitbucket_server" do
+ context 'with no optional parameters' do
+ let_it_be(:project) { create(:project) }
+ let(:client) { double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(client.as_null_object)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 201 response when the project is imported successfully' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, repo_slug, user.namespace, user, anything)
+ .and_return(double(execute: project))
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['name']).to eq(project.name)
+ end
+ end
+
+ context 'with a new project name' do
+ let_it_be(:project) { create(:project, name: 'new-name') }
+ let(:client) { instance_double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(client)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 201 response when the project is imported successfully with a new project name' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, project.name, user.namespace, user, anything)
+ .and_return(double(execute: project))
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_name: 'new-name'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['name']).to eq('new-name')
+ end
+ end
+
+ context 'with an invalid URL' do
+ let_it_be(:project) { create(:project, name: 'new-name') }
+ let(:client) { instance_double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(client)
+ allow(client).to receive(:repo).with(project_key, repo_slug).and_return(double(name: repo_slug))
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 400 response due to a blcoked URL' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, project.name, user.namespace, user, anything)
+ .and_return(double(execute: project))
+
+ allow(Gitlab::UrlBlocker)
+ .to receive(:blocked_url?)
+ .and_return(true)
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_name: 'new-name'
+ }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'with a new namespace' do
+ let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(bitbucket_client)
+ repo = double(name: repo_slug, full_path: "/other-namespace/#{repo_slug}")
+ allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_return(repo)
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 201 response when the project is imported successfully to a new namespace' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, repo_slug, an_instance_of(Group), user, anything)
+ .and_return(double(execute: create(:project, name: repo_slug)))
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_namespace: 'new-namespace'
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_a Hash
+ expect(json_response['full_path']).not_to eq("/#{user.namespace}/#{repo_slug}")
+ end
+ end
+
+ context 'with a private inaccessible namespace' do
+ let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
+ let(:project) { create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim', namespace: 'private-group/vim') }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(bitbucket_client)
+ repo = double(name: repo_slug, full_path: "/private-group/#{repo_slug}")
+ allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_return(repo)
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'returns 401 response when user can not create projects in the chosen namespace' do
+ allow(Gitlab::BitbucketServerImport::ProjectCreator)
+ .to receive(:new).with(project_key, repo_slug, anything, repo_slug, an_instance_of(Group), user, anything)
+ .and_return(double(execute: build(:project)))
+
+ other_namespace = create(:group, :private, name: 'private-group')
+
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_namespace: other_namespace.name
+ }
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'with an inaccessible bitbucket server instance' do
+ let(:bitbucket_client) { instance_double(BitbucketServer::Client) }
+ let(:project) { create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim', namespace: 'private-group/vim') }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(bitbucket_client)
+ allow(bitbucket_client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'raises a connection error' do
+ post api("/import/bitbucket_server", user), params: {
+ bitbucket_server_url: base_uri,
+ bitbucket_server_username: user,
+ personal_access_token: token,
+ bitbucket_server_project: project_key,
+ bitbucket_server_repo: repo_slug,
+ new_namespace: 'new-namespace'
+ }
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb
index f33436df40e..f026314f7a8 100644
--- a/spec/requests/api/import_github_spec.rb
+++ b/spec/requests/api/import_github_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ImportGithub do
+RSpec.describe API::ImportGithub do
let(:token) { "asdasd12345" }
let(:provider) { :github }
let(:access_params) { { github_access_token: token } }
@@ -26,6 +26,10 @@ describe API::ImportGithub do
end
end
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
it 'rejects requests when Github Importer is disabled' do
stub_application_setting(import_sources: nil)
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index aa5e2367a2b..7d219954e9d 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Internal::Base do
+RSpec.describe API::Internal::Base do
let_it_be(:user, reload: true) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository, :wiki_repo) }
let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) }
@@ -467,21 +467,6 @@ describe API::Internal::Base do
expect(json_response["git_config_options"]).to include("uploadpack.allowFilter=true")
expect(json_response["git_config_options"]).to include("uploadpack.allowAnySHA1InWant=true")
end
-
- context 'when gitaly_upload_pack_filter feature flag is disabled' do
- before do
- stub_feature_flags(gitaly_upload_pack_filter: false)
- end
-
- it 'returns only maxInputSize and not partial clone git config' do
- push(key, project)
-
- expect(json_response["git_config_options"]).to be_present
- expect(json_response["git_config_options"]).to include("receive.maxInputSize=1048576")
- expect(json_response["git_config_options"]).not_to include("uploadpack.allowFilter=true")
- expect(json_response["git_config_options"]).not_to include("uploadpack.allowAnySHA1InWant=true")
- end
- end
end
context 'when receive_max_input_size is empty' do
@@ -496,18 +481,6 @@ describe API::Internal::Base do
expect(json_response["git_config_options"]).to include("uploadpack.allowFilter=true")
expect(json_response["git_config_options"]).to include("uploadpack.allowAnySHA1InWant=true")
end
-
- context 'when gitaly_upload_pack_filter feature flag is disabled' do
- before do
- stub_feature_flags(gitaly_upload_pack_filter: false)
- end
-
- it 'returns an empty git config' do
- push(key, project)
-
- expect(json_response["git_config_options"]).to be_empty
- end
- end
end
end
diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb
index fecf15c29c2..48fc95b6574 100644
--- a/spec/requests/api/internal/pages_spec.rb
+++ b/spec/requests/api/internal/pages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Internal::Pages do
+RSpec.describe API::Internal::Pages do
let(:auth_headers) do
jwt_token = JWT.encode({ 'iss' => 'gitlab-pages' }, Gitlab::Pages.secret, 'HS256')
{ Gitlab::Pages::INTERNAL_API_REQUEST_HEADER => jwt_token }
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
index 5c925d2a32e..b53fac3679d 100644
--- a/spec/requests/api/issues/get_group_issues_spec.rb
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user2) { create(:user) }
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:non_member) { create(:user) }
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index 4a728c81215..7ff07bf580d 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace) }
let_it_be(:private_mrs_project) do
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index 315396c89c3..519bea22501 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace) }
let_it_be(:private_mrs_project) do
@@ -886,4 +886,53 @@ describe API::Issues do
include_examples 'time tracking endpoints', 'issue'
end
+
+ describe 'PUT /projects/:id/issues/:issue_iid/reorder' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue1) { create(:issue, project: project, relative_position: 10) }
+ let_it_be(:issue2) { create(:issue, project: project, relative_position: 20) }
+ let_it_be(:issue3) { create(:issue, project: project, relative_position: 30) }
+
+ context 'when user has access' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'with valid params' do
+ it 'reorders issues and returns a successful 200 response' do
+ put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(issue1.reload.relative_position)
+ .to be_between(issue2.reload.relative_position, issue3.reload.relative_position)
+ end
+ end
+
+ context 'with invalid params' do
+ it 'returns a unprocessable entity 422 response for invalid move ids' do
+ put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: non_existing_record_id }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+
+ it 'returns a not found 404 response for invalid issue id' do
+ put api("/projects/#{project.id}/issues/#{non_existing_record_iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'with unauthorized user' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'responds with 403 forbidden' do
+ put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb
index 2e1e5d3204e..e2f1bb2cd1a 100644
--- a/spec/requests/api/issues/post_projects_issues_spec.rb
+++ b/spec/requests/api/issues/post_projects_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) do
create(:project, :public, creator_id: user.id, namespace: user.namespace)
diff --git a/spec/requests/api/issues/put_projects_issues_spec.rb b/spec/requests/api/issues/put_projects_issues_spec.rb
index 62a4d3b48b2..dac721cbea0 100644
--- a/spec/requests/api/issues/put_projects_issues_spec.rb
+++ b/spec/requests/api/issues/put_projects_issues_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Issues do
+RSpec.describe API::Issues do
let_it_be(:user) { create(:user) }
let_it_be(:owner) { create(:owner) }
let(:user2) { create(:user) }
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 18b5c00d64f..53c57931d36 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Jobs do
+RSpec.describe API::Jobs do
include HttpIOHelpers
shared_examples 'a job with artifacts and trace' do |result_is_array: true|
@@ -36,9 +36,9 @@ describe API::Jobs do
end
let_it_be(:pipeline, reload: true) do
- create(:ci_empty_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!(:job) do
diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb
index 089ee22982c..49b8f4a8520 100644
--- a/spec/requests/api/keys_spec.rb
+++ b/spec/requests/api/keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Keys do
+RSpec.describe API::Keys do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:key) { create(:key, user: user, expires_at: 1.day.from_now) }
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 697f22e5f29..fc674fca9b2 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Labels do
+RSpec.describe API::Labels do
def put_labels_api(route_type, user, spec_params, request_params = {})
if route_type == :deprecated
put api("/projects/#{project.id}/labels", user),
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index 71c2619d898..4c60c8bd2a3 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Lint do
+RSpec.describe API::Lint do
describe 'POST /ci/lint' do
context 'with valid .gitlab-ci.yaml content' do
let(:yaml_content) do
diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb
index 53e43430b1f..35d91963ac9 100644
--- a/spec/requests/api/markdown_spec.rb
+++ b/spec/requests/api/markdown_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::Markdown do
+RSpec.describe API::Markdown do
describe "POST /markdown" do
let(:user) {} # No-op. It gets overwritten in the contexts below.
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
new file mode 100644
index 00000000000..189d6a4c1a4
--- /dev/null
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -0,0 +1,569 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::MavenPackages do
+ include WorkhorseHelpers
+
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
+ let_it_be(:package, reload: true) { create(:maven_package, project: project, name: project.full_path) }
+ let_it_be(:maven_metadatum, reload: true) { package.maven_metadatum }
+ let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first }
+ let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:job) { create(:ci_build, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:headers_with_token) { headers.merge('Private-Token' => personal_access_token.token) }
+
+ let(:headers_with_deploy_token) do
+ headers.merge(
+ Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token.token
+ )
+ end
+
+ let(:version) { '1.0-SNAPSHOT' }
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples 'tracking the file download event' do
+ context 'with jar file' do
+ let_it_be(:package_file) { jar_file }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+ end
+
+ shared_examples 'processing HEAD requests' do
+ subject { head api(url) }
+
+ before do
+ allow_any_instance_of(::Packages::PackageFileUploader).to receive(:fog_credentials).and_return(object_storage_credentials)
+ stub_package_file_object_storage(enabled: object_storage_enabled)
+ end
+
+ context 'with object storage enabled' do
+ let(:object_storage_enabled) { true }
+
+ before do
+ allow_any_instance_of(::Packages::PackageFileUploader).to receive(:file_storage?).and_return(false)
+ end
+
+ context 'non AWS provider' do
+ let(:object_storage_credentials) { { provider: 'Google' } }
+
+ it 'does not generated a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+ subject
+ end
+ end
+
+ context 'with AWS provider' do
+ let(:object_storage_credentials) { { provider: 'AWS', aws_access_key_id: 'test', aws_secret_access_key: 'test' } }
+
+ it 'generates a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).to receive(:head_url).and_call_original
+
+ subject
+ end
+ end
+ end
+
+ context 'with object storage disabled' do
+ let(:object_storage_enabled) { false }
+ let(:object_storage_credentials) { {} }
+
+ it 'does not generate a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+ subject
+ end
+ end
+ end
+
+ shared_examples 'downloads with a deploy token' do
+ it 'allows download with deploy token' do
+ download_file(
+ package_file.file_name,
+ {},
+ Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token.token
+ )
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'downloads with a job token' do
+ it 'allows download with job token' do
+ download_file(package_file.file_name, job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ describe 'GET /api/v4/packages/maven/*path/:file_name' do
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'private project' do
+ subject { download_file_with_token(package_file.file_name) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'project name is different from a package name' do
+ before do
+ maven_metadatum.update!(path: "wrong_name/#{package.version}")
+ end
+
+ it 'rejects request' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/packages/maven/#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/packages/maven/*path/:file_name' do
+ let(:url) { "/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'GET /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
+ before do
+ project.team.truncate
+ group.add_developer(user)
+ end
+
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ group.group_member(user).destroy
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ group.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/groups/#{group.id}/-/packages/maven/#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
+ let(:url) { "/groups/#{group.id}/-/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/projects/#{project.id}/packages/maven/" \
+ "#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ let(:url) { "/projects/#{project.id}/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name/authorize' do
+ it 'rejects a malicious request' do
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2F.ssh%2Fauthorized_keys/authorize"), params: {}, headers: headers_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'authorizes posting package with a valid token' do
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).not_to be_nil
+ end
+
+ it 'rejects request without a valid token' do
+ headers_with_token['Private-Token'] = 'foo'
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'rejects request without a valid permission' do
+ project.add_guest(user)
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'rejects requests that did not go through gitlab-workhorse' do
+ headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'authorizes upload with job token' do
+ authorize_upload(job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'authorizes upload with deploy token' do
+ authorize_upload({}, headers_with_deploy_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ 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
+
+ def authorize_upload_with_token(params = {}, request_headers = headers_with_token)
+ authorize_upload(params, request_headers)
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:send_rewritten_field) { true }
+ let(:file_upload) { fixture_file_upload('spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar') }
+
+ before do
+ # by configuring this path we allow to pass temp file from any path
+ allow(Packages::PackageFileUploader).to receive(:workhorse_upload_path).and_return('/')
+ end
+
+ it 'rejects requests without a file from workhorse' do
+ upload_file_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'rejects request without a token' do
+ upload_file
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ context 'without workhorse rewritten field' do
+ let(:send_rewritten_field) { false }
+
+ it 'rejects the request' do
+ upload_file_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when params from workhorse are correct' do
+ let(:params) { { file: file_upload } }
+
+ it 'rejects a malicious request' do
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2f.ssh%2fauthorized_keys"), params: params, headers: headers_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ context 'without workhorse header' do
+ let(:workhorse_header) { {} }
+
+ subject { upload_file_with_token(params) }
+
+ it_behaves_like 'package workhorse uploads'
+ end
+
+ context 'event tracking' do
+ subject { upload_file_with_token(params) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+
+ it 'creates package and stores package file' do
+ expect { upload_file_with_token(params) }.to change { project.packages.count }.by(1)
+ .and change { Packages::Maven::Metadatum.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(jar_file.file_name).to eq(file_upload.original_filename)
+ end
+
+ it 'allows upload with job token' do
+ upload_file(params.merge(job_token: job.token))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline
+ end
+
+ it 'allows upload with deploy token' do
+ upload_file(params, headers_with_deploy_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'version is not correct' do
+ let(:version) { '$%123' }
+
+ it 'rejects request' do
+ expect { upload_file_with_token(params) }.not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to include('Validation failed')
+ end
+ end
+ end
+
+ def upload_file(params = {}, request_headers = headers)
+ url = "/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/my-app-1.0-20180724.124855-1.jar"
+ workhorse_finalize(
+ api(url),
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: request_headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ def upload_file_with_token(params = {}, request_headers = headers_with_token)
+ upload_file(params, request_headers)
+ end
+ end
+end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 0ecef26c27a..23889912d7a 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Members do
+RSpec.describe API::Members do
let(:maintainer) { create(:user, username: 'maintainer_user') }
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
@@ -321,6 +321,26 @@ describe API::Members do
expect(response).to have_gitlab_http_status(:bad_request)
end
end
+
+ context 'adding project bot' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+
+ before do
+ unrelated_project = create(:project)
+ unrelated_project.add_maintainer(project_bot)
+ end
+
+ it 'returns 400' do
+ expect do
+ post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
+ params: { user_id: project_bot.id, access_level: Member::DEVELOPER }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']['user_id']).to(
+ include('project bots cannot be added to other groups / projects'))
+ end.not_to change { project.members.count }
+ end
+ end
end
shared_examples 'PUT /:source_type/:id/members/:user_id' do |source_type|
@@ -461,8 +481,34 @@ describe API::Members do
end
end
- it_behaves_like 'POST /:source_type/:id/members', 'project' do
- let(:source) { project }
+ describe 'POST /projects/:id/members' do
+ it_behaves_like 'POST /:source_type/:id/members', 'project' do
+ let(:source) { project }
+ end
+
+ context 'adding owner to project' do
+ it 'returns 403' do
+ expect do
+ post api("/projects/#{project.id}/members", maintainer),
+ params: { user_id: stranger.id, access_level: Member::OWNER }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end.not_to change { project.members.count }
+ end
+ end
+
+ context 'remove bot from project' do
+ it 'returns a 403 forbidden' do
+ project_bot = create(:user, :project_bot)
+ create(:project_member, project: project, user: project_bot)
+
+ expect do
+ delete api("/projects/#{project.id}/members/#{project_bot.id}", maintainer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { project.members.count }
+ end
+ end
end
it_behaves_like 'POST /:source_type/:id/members', 'group' do
@@ -484,15 +530,4 @@ describe API::Members do
it_behaves_like 'DELETE /:source_type/:id/members/:user_id', 'group' do
let(:source) { group }
end
-
- context 'Adding owner to project' do
- it 'returns 403' do
- expect do
- post api("/projects/#{project.id}/members", maintainer),
- params: { user_id: stranger.id, access_level: Member::OWNER }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end.to change { project.members.count }.by(0)
- end
- end
end
diff --git a/spec/requests/api/merge_request_approvals_spec.rb b/spec/requests/api/merge_request_approvals_spec.rb
new file mode 100644
index 00000000000..fad5c3fb60e
--- /dev/null
+++ b/spec/requests/api/merge_request_approvals_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::MergeRequestApprovals do
+ let_it_be(:user) { create(:user) }
+ 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
+ it 'retrieves the approval status' do
+ project.add_developer(approver)
+ project.add_developer(create(:user))
+
+ create(:approval, user: approver, merge_request: merge_request)
+
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approvals", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ describe 'POST :id/merge_requests/:merge_request_iid/approve' do
+ context 'as a valid approver' do
+ let_it_be(:approver) { create(:user) }
+
+ before do
+ project.add_developer(approver)
+ project.add_developer(create(:user))
+ end
+
+ def approve(extra_params = {})
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/approve", approver), params: extra_params
+ end
+
+ context 'when the sha param is not set' do
+ it 'approves the merge request' do
+ approve
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when the sha param is correct' do
+ it 'approves the merge request' do
+ approve(sha: merge_request.diff_head_sha)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when the sha param is incorrect' do
+ it 'does not approve the merge request' do
+ approve(sha: merge_request.diff_head_sha.reverse)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ expect(merge_request.approvals).to be_empty
+ end
+ end
+ end
+ end
+
+ describe 'POST :id/merge_requests/:merge_request_iid/unapprove' do
+ context 'as a user who has approved the merge request' do
+ it 'unapproves the merge request' 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)
+
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unapprove", unapprover)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb
index d00bc4a6dde..3f41a7a034d 100644
--- a/spec/requests/api/merge_request_diffs_spec.rb
+++ b/spec/requests/api/merge_request_diffs_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
+RSpec.describe API::MergeRequestDiffs, 'MergeRequestDiffs' do
let!(:user) { create(:user) }
let!(:merge_request) { create(:merge_request, importing: true) }
let!(:project) { merge_request.target_project }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 7a0077f853a..68f1a0f1ba1 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::MergeRequests do
+RSpec.describe API::MergeRequests do
include ProjectForksHelper
let(:base_time) { Time.now }
@@ -425,6 +425,73 @@ describe API::MergeRequests do
end
end
+ context 'NOT params' do
+ let(:merge_request2) do
+ create(
+ :merge_request,
+ :simple,
+ milestone: milestone,
+ author: user,
+ assignees: [user],
+ merge_request_context_commits: [merge_request_context_commit],
+ source_project: project,
+ target_project: project,
+ source_branch: 'what',
+ title: "What",
+ created_at: base_time
+ )
+ end
+
+ before do
+ create(:label_link, label: label, target: merge_request)
+ create(:label_link, label: label2, target: merge_request2)
+ end
+
+ it 'returns merge requests without any of the labels given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { labels: ["#{label.title}, #{label2.title}"] } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(3)
+ json_response.each do |mr|
+ expect(mr['labels']).not_to include(label2.title, label.title)
+ end
+ end
+
+ it 'returns merge requests without any of the milestones given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { milestone: milestone.title } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(4)
+ json_response.each do |mr|
+ expect(mr['milestone']).not_to eq(milestone.title)
+ end
+ end
+
+ it 'returns merge requests without the author given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { author_id: user2.id } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(5)
+ json_response.each do |mr|
+ expect(mr['author']['id']).not_to eq(user2.id)
+ end
+ end
+
+ it 'returns merge requests without the assignee given', :aggregate_failures do
+ get api(endpoint_path, user), params: { not: { assignee_id: user2.id } }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(5)
+ json_response.each do |mr|
+ expect(mr['assignee']['id']).not_to eq(user2.id)
+ end
+ end
+ end
+
context 'source_branch param' do
it 'returns merge requests with the given source branch' do
get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
@@ -1930,7 +1997,7 @@ describe API::MergeRequests do
it "updates the MR's squash attribute" do
expect do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { squash: true }
- end.to change { merge_request.reload.squash }
+ end.to change { merge_request.reload.squash_on_merge? }
expect(response).to have_gitlab_http_status(:ok)
end
diff --git a/spec/requests/api/metrics/dashboard/annotations_spec.rb b/spec/requests/api/metrics/dashboard/annotations_spec.rb
index 6377ef2435a..07de2925ee2 100644
--- a/spec/requests/api/metrics/dashboard/annotations_spec.rb
+++ b/spec/requests/api/metrics/dashboard/annotations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Metrics::Dashboard::Annotations do
+RSpec.describe API::Metrics::Dashboard::Annotations do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private, :repository, namespace: user.namespace) }
let_it_be(:environment) { create(:environment, project: project) }
diff --git a/spec/requests/api/metrics/user_starred_dashboards_spec.rb b/spec/requests/api/metrics/user_starred_dashboards_spec.rb
index 8f9394a0e20..533dff05f27 100644
--- a/spec/requests/api/metrics/user_starred_dashboards_spec.rb
+++ b/spec/requests/api/metrics/user_starred_dashboards_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Metrics::UserStarredDashboards do
+RSpec.describe API::Metrics::UserStarredDashboards do
let_it_be(:user) { create(:user) }
let_it_be(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
let_it_be(:dashboard) { '.gitlab/dashboards/find&seek.yml' }
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 68fffc638df..2ac76d469d5 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Namespaces do
+RSpec.describe API::Namespaces do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let!(:group1) { create(:group, name: 'group.one') }
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 797dd3bb4e2..1510d31a1a6 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Notes do
+RSpec.describe API::Notes do
let!(:user) { create(:user) }
let!(:project) { create(:project, :public) }
let(:private_user) { create(:user) }
diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb
index 2dfde4c8ec9..73cb4948524 100644
--- a/spec/requests/api/notification_settings_spec.rb
+++ b/spec/requests/api/notification_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::NotificationSettings do
+RSpec.describe API::NotificationSettings do
let(:user) { create(:user) }
let!(:group) { create(:group) }
let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) }
diff --git a/spec/requests/api/npm_packages_spec.rb b/spec/requests/api/npm_packages_spec.rb
new file mode 100644
index 00000000000..98a1ca978a8
--- /dev/null
+++ b/spec/requests/api/npm_packages_spec.rb
@@ -0,0 +1,550 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::NpmPackages do
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
+ let_it_be(:package, reload: true) { create(:npm_package, project: project) }
+ let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:job) { create(:ci_build, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples 'a package that requires auth' do
+ it 'returns the package info with oauth token' do
+ get_package_with_token(package)
+
+ expect_a_valid_package_response
+ end
+
+ it 'returns the package info with job token' do
+ get_package_with_job_token(package)
+
+ expect_a_valid_package_response
+ end
+
+ it 'denies request without oauth token' do
+ get_package(package)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns the package info with deploy token' do
+ get_package_with_deploy_token(package)
+
+ expect_a_valid_package_response
+ end
+ end
+
+ describe 'GET /api/v4/packages/npm/*package_name' do
+ let_it_be(:package_dependency_link1) { create(:packages_dependency_link, package: package, dependency_type: :dependencies) }
+ let_it_be(:package_dependency_link2) { create(:packages_dependency_link, package: package, dependency_type: :devDependencies) }
+ let_it_be(:package_dependency_link3) { create(:packages_dependency_link, package: package, dependency_type: :bundleDependencies) }
+ let_it_be(:package_dependency_link4) { create(:packages_dependency_link, package: package, dependency_type: :peerDependencies) }
+
+ shared_examples 'returning the npm package info' do
+ it 'returns the package info' do
+ get_package(package)
+
+ expect_a_valid_package_response
+ end
+ end
+
+ shared_examples 'returning forbidden for unknown package' do
+ context 'with an unknown package' do
+ it 'returns forbidden' do
+ get api("/packages/npm/unknown")
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ context 'a public project' do
+ it_behaves_like 'returning the npm package info'
+
+ context 'with application setting enabled' do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: true)
+ end
+
+ it_behaves_like 'returning the npm package info'
+
+ context 'with unknown package' do
+ it 'returns a redirect' do
+ get api("/packages/npm/unknown")
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response.headers['Location']).to eq('https://registry.npmjs.org/unknown')
+ end
+ end
+ end
+
+ context 'with application setting disabled' do
+ before do
+ stub_application_setting(npm_package_requests_forwarding: false)
+ end
+
+ it_behaves_like 'returning the npm package info'
+
+ it_behaves_like 'returning forbidden for unknown package'
+ end
+
+ context 'project path with a dot' do
+ before do
+ project.update!(path: 'foo.bar')
+ end
+
+ it_behaves_like 'returning the npm package info'
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'a package that requires auth'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'a package that requires auth'
+
+ it 'denies request when not enough permissions' do
+ project.add_guest(user)
+
+ get_package_with_token(package)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ def get_package(package, params = {}, headers = {})
+ get api("/packages/npm/#{package.name}"), params: params, headers: headers
+ end
+
+ def get_package_with_token(package, params = {})
+ get_package(package, params.merge(access_token: token.token))
+ end
+
+ def get_package_with_job_token(package, params = {})
+ get_package(package, params.merge(job_token: job.token))
+ end
+
+ def get_package_with_deploy_token(package, params = {})
+ get_package(package, {}, build_token_auth_header(deploy_token.token))
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do
+ let_it_be(:package_file) { package.package_files.first }
+
+ shared_examples 'a package file that requires auth' do
+ it 'returns the file with an access token' do
+ get_file_with_token(package_file)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns the file with a job token' do
+ get_file_with_job_token(package_file)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download with no token' do
+ get_file(package_file)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'a public project' do
+ subject { get_file(package_file) }
+
+ it 'returns the file with no token needed' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'a package file that requires auth'
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ get_file_with_token(package_file)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ it_behaves_like 'a package file that requires auth'
+ end
+
+ def get_file(package_file, params = {})
+ get api("/projects/#{project.id}/packages/npm/" \
+ "#{package_file.package.name}/-/#{package_file.file_name}"), params: params
+ end
+
+ def get_file_with_token(package_file, params = {})
+ get_file(package_file, params.merge(access_token: token.token))
+ end
+
+ def get_file_with_job_token(package_file, params = {})
+ get_file(package_file, params.merge(job_token: job.token))
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do
+ RSpec.shared_examples 'handling invalid record with 400 error' do
+ it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do
+ expect { upload_package_with_token(package_name, params) }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when params are correct' do
+ context 'invalid package record' do
+ context 'unscoped package' do
+ let(:package_name) { 'my_unscoped_package' }
+ let(:params) { upload_params(package_name: package_name) }
+
+ it_behaves_like 'handling invalid record with 400 error'
+
+ context 'with empty versions' do
+ let(:params) { upload_params(package_name: package_name).merge!(versions: {}) }
+
+ it 'throws a 400 error' do
+ expect { upload_package_with_token(package_name, params) }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ context 'invalid package name' do
+ let(:package_name) { "@#{group.path}/my_inv@@lid_package_name" }
+ let(:params) { upload_params(package_name: package_name) }
+
+ it_behaves_like 'handling invalid record with 400 error'
+ end
+
+ context 'invalid package version' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:package_name) { "@#{group.path}/my_package_name" }
+
+ where(:version) do
+ [
+ '1',
+ '1.2',
+ '1./2.3',
+ '../../../../../1.2.3',
+ '%2e%2e%2f1.2.3'
+ ]
+ end
+
+ with_them do
+ let(:params) { upload_params(package_name: package_name, package_version: version) }
+
+ it_behaves_like 'handling invalid record with 400 error'
+ end
+ end
+ end
+
+ context 'scoped package' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name) }
+
+ context 'with access token' do
+ subject { upload_package_with_token(package_name, params) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+
+ it 'creates npm package with file' do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Tag.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'creates npm package with file with job token' do
+ expect { upload_package_with_job_token(package_name, params) }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with an authenticated job token' do
+ let!(:job) { create(:ci_build, user: user) }
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ expect(endpoint).to receive(:current_authenticated_job) { job }
+ end
+ end
+
+ after do
+ Grape::Endpoint.before_each nil
+ end
+
+ it 'creates the package metadata' do
+ upload_package_with_token(package_name, params)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.reload.packages.find(json_response['id']).build_info.pipeline).to eq job.pipeline
+ end
+ end
+ end
+
+ context 'package creation fails' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name) }
+
+ it 'returns an error if the package already exists' do
+ create(:npm_package, project: project, version: '1.0.1', name: "@#{group.path}/my_package_name")
+ expect { upload_package_with_token(package_name, params) }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'with dependencies' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) { upload_params(package_name: package_name, file: 'npm/payload_with_duplicated_packages.json') }
+
+ it 'creates npm package with file and dependencies' do
+ expect { upload_package_with_token(package_name, params) }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Dependency.count}.by(4)
+ .and change { Packages::DependencyLink.count}.by(6)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'with existing dependencies' do
+ before do
+ name = "@#{group.path}/existing_package"
+ upload_package_with_token(name, upload_params(package_name: name, file: 'npm/payload_with_duplicated_packages.json'))
+ end
+
+ it 'reuses them' do
+ expect { upload_package_with_token(package_name, params) }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and not_change { Packages::Dependency.count}
+ .and change { Packages::DependencyLink.count}.by(6)
+ end
+ end
+ end
+ end
+
+ def upload_package(package_name, params = {})
+ put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params: params
+ end
+
+ def upload_package_with_token(package_name, params = {})
+ upload_package(package_name, params.merge(access_token: token.token))
+ end
+
+ def upload_package_with_job_token(package_name, params = {})
+ upload_package(package_name, params.merge(job_token: job.token))
+ end
+
+ def upload_params(package_name:, package_version: '1.0.1', file: 'npm/payload.json')
+ Gitlab::Json.parse(fixture_file("packages/#{file}")
+ .gsub('@root/npm-test', package_name)
+ .gsub('1.0.1', package_version))
+ end
+ end
+
+ describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do
+ let_it_be(:package_tag1) { create(:packages_tag, package: package) }
+ let_it_be(:package_tag2) { create(:packages_tag, package: package) }
+
+ let(:package_name) { package.name }
+ let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with public project' do
+ context 'with authenticated user' do
+ subject { get api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'returns package tags', :maintainer
+ it_behaves_like 'returns package tags', :developer
+ it_behaves_like 'returns package tags', :reporter
+ it_behaves_like 'returns package tags', :guest
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'returns package tags', :no_type
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ subject { get api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'returns package tags', :maintainer
+ it_behaves_like 'returns package tags', :developer
+ it_behaves_like 'returns package tags', :reporter
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :forbidden
+ end
+ end
+ end
+ end
+
+ describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ let_it_be(:tag_name) { 'test' }
+
+ let(:package_name) { package.name }
+ let(:version) { package.version }
+ let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}" }
+
+ subject { put api(url), env: { 'api.request.body': version } }
+
+ context 'without the need for a license' do
+ context 'with public project' do
+ context 'with authenticated user' do
+ subject { put api(url, personal_access_token: personal_access_token), env: { 'api.request.body': version } }
+
+ it_behaves_like 'create package tag', :maintainer
+ it_behaves_like 'create package tag', :developer
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ subject { put api(url, personal_access_token: personal_access_token), env: { 'api.request.body': version } }
+
+ it_behaves_like 'create package tag', :maintainer
+ it_behaves_like 'create package tag', :developer
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ let_it_be(:package_tag) { create(:packages_tag, package: package) }
+
+ let(:package_name) { package.name }
+ let(:tag_name) { package_tag.name }
+ let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}" }
+
+ subject { delete api(url) }
+
+ context 'without the need for a license' do
+ context 'with public project' do
+ context 'with authenticated user' do
+ subject { delete api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'delete package tag', :maintainer
+ it_behaves_like 'rejects package tags access', :developer, :forbidden
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+
+ context 'with private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'with authenticated user' do
+ subject { delete api(url, personal_access_token: personal_access_token) }
+
+ it_behaves_like 'delete package tag', :maintainer
+ it_behaves_like 'rejects package tags access', :developer, :forbidden
+ it_behaves_like 'rejects package tags access', :reporter, :forbidden
+ it_behaves_like 'rejects package tags access', :guest, :forbidden
+ end
+
+ context 'with unauthenticated user' do
+ it_behaves_like 'rejects package tags access', :no_type, :unauthorized
+ end
+ end
+ end
+ end
+
+ def expect_a_valid_package_response
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/json')
+ expect(response).to match_response_schema('public_api/v4/packages/npm_package')
+ expect(json_response['name']).to eq(package.name)
+ expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version')
+ ::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
+ expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any
+ end
+ expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags')
+ end
+end
diff --git a/spec/requests/api/nuget_packages_spec.rb b/spec/requests/api/nuget_packages_spec.rb
new file mode 100644
index 00000000000..43aa65d1f76
--- /dev/null
+++ b/spec/requests/api/nuget_packages_spec.rb
@@ -0,0 +1,482 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::NugetPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ describe 'GET /api/v4/projects/:id/packages/nuget' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/index.json" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do
+ let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/authorize" }
+ let(:headers) { {} }
+
+ subject { put api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/nuget' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let_it_be(:file_name) { 'package.nupkg' }
+ let(:url) { "/projects/#{project.id}/packages/nuget" }
+ let(:headers) { {} }
+ let(:params) { { package: temp_file(file_name) } }
+ let(:file_key) { :package }
+ let(:send_rewritten_field) { true }
+
+ subject do
+ workhorse_finalize(
+ api(url),
+ method: :put,
+ file_key: file_key,
+ params: params,
+ headers: headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
+ 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
+ let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" }
+
+ subject { get api(url) }
+
+ before do
+ packages.each { |pkg| create_dependencies_for(pkg) }
+ end
+
+ context 'without the need for license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
+ include_context 'with expected presenters dependency groups'
+
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
+ let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" }
+
+ subject { get api(url) }
+
+ before do
+ create_dependencies_for(package)
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ context 'with invalid package name' do
+ let_it_be(:package_name) { 'Unkown' }
+
+ it_behaves_like 'rejects nuget packages access', :developer, :not_found
+ end
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) }
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
+ let_it_be(:package_name) { 'Dummy.Package' }
+ let_it_be(:package) { create(:nuget_package, project: project, name: package_name) }
+
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/nuget/query' do
+ let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) }
+ let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') }
+ let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) }
+ let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) }
+ let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) }
+ let_it_be(:package_e) { create(:nuget_package, name: 'Foo.BarE', project: project) }
+ let(:search_term) { 'uMmy' }
+ let(:take) { 26 }
+ let(:skip) { 0 }
+ let(:include_prereleases) { true }
+ let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } }
+ let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | true | true | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | true | false | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | true | false | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | false | true | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | false | true | 'process nuget search request' | :success
+ 'PUBLIC' | :developer | false | false | 'process nuget search request' | :success
+ 'PUBLIC' | :guest | false | false | 'process nuget search request' | :success
+ 'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success
+ 'PRIVATE' | :developer | true | true | 'process nuget search request' | :success
+ 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
+ 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects nuget access with unknown project id'
+
+ it_behaves_like 'rejects nuget access with invalid project id'
+ end
+ end
+end
diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb
index 5e775841f12..f5971054b3c 100644
--- a/spec/requests/api/oauth_tokens_spec.rb
+++ b/spec/requests/api/oauth_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OAuth tokens' do
+RSpec.describe 'OAuth tokens' do
include HttpBasicAuthHelpers
context 'Resource Owner Password Credentials' do
diff --git a/spec/requests/api/package_files_spec.rb b/spec/requests/api/package_files_spec.rb
new file mode 100644
index 00000000000..11170066d6e
--- /dev/null
+++ b/spec/requests/api/package_files_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::PackageFiles do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:package) { create(:maven_package, project: project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe 'GET /projects/:id/packages/:package_id/package_files' do
+ let(:url) { "/projects/#{project.id}/packages/#{package.id}/package_files" }
+
+ context 'without the need for a license' do
+ context 'project is public' do
+ it 'returns 200' do
+ get api(url)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns 404 if package does not exist' do
+ get api("/projects/#{project.id}/packages/0/package_files")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404 for non authenticated user' do
+ get api(url)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for a user without access to the project' do
+ project.team.truncate
+
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 200 and valid response schema' do
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package_files')
+ end
+ end
+
+ context 'with pagination params' do
+ let(:per_page) { 2 }
+ let!(:package_file_1) { package.package_files[0] }
+ let!(:package_file_2) { package.package_files[1] }
+ let!(:package_file_3) { package.package_files[2] }
+
+ context 'when viewing the first page' do
+ it 'returns first 2 packages' do
+ get api(url, user), params: { page: 1, per_page: per_page }
+
+ expect_paginated_array_response([package_file_1.id, package_file_2.id])
+ end
+ end
+
+ context 'viewing the second page' do
+ it 'returns the last package' do
+ get api(url, user), params: { page: 2, per_page: per_page }
+
+ expect_paginated_array_response([package_file_3.id])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb
index ee55d1c54b7..c894a2d3ca4 100644
--- a/spec/requests/api/pages/internal_access_spec.rb
+++ b/spec/requests/api/pages/internal_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Internal Project Pages Access" do
+RSpec.describe "Internal Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages/pages_spec.rb b/spec/requests/api/pages/pages_spec.rb
index 62d43ecff16..53e732928ff 100644
--- a/spec/requests/api/pages/pages_spec.rb
+++ b/spec/requests/api/pages/pages_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Pages do
+RSpec.describe API::Pages do
let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) }
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb
index 146c6a389f3..ea5db691b14 100644
--- a/spec/requests/api/pages/private_access_spec.rb
+++ b/spec/requests/api/pages/private_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Private Project Pages Access" do
+RSpec.describe "Private Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb
index 7d929e2a287..ae73cee91d5 100644
--- a/spec/requests/api/pages/public_access_spec.rb
+++ b/spec/requests/api/pages/public_access_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe "Public Project Pages Access" do
+RSpec.describe "Public Project Pages Access" do
using RSpec::Parameterized::TableSyntax
include AccessMatchers
diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb
index 8c411233b27..b6838a39257 100644
--- a/spec/requests/api/pages_domains_spec.rb
+++ b/spec/requests/api/pages_domains_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::PagesDomains do
+RSpec.describe API::PagesDomains do
let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index ed899e830e1..ff35e380476 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectClusters do
+RSpec.describe API::ProjectClusters do
include KubernetesHelpers
let_it_be(:current_user) { create(:user) }
@@ -40,7 +40,7 @@ describe API::ProjectClusters do
expect(response).to include_pagination_headers
end
- it 'onlies include authorized clusters' do
+ it 'only includes authorized clusters' do
cluster_ids = json_response.map { |cluster| cluster['id'] }
expect(response).to have_gitlab_http_status(:ok)
@@ -258,29 +258,52 @@ describe API::ProjectClusters do
end
end
- context 'when user tries to add multiple clusters' do
+ context 'non-authorized user' do
before do
- create(:cluster, :provided_by_gcp, :project,
- projects: [project])
-
- post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
+ post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
end
- it 'responds with 400' do
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']['base'].first)
- .to eq(_('Instance does not support multiple Kubernetes clusters'))
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
+ end
- context 'non-authorized user' do
+ describe 'POST /projects/:id/clusters/user with multiple clusters' do
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:namespace) { project.path }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ namespace: namespace
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ environment_scope: 'production/*',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'when another cluster exists' do
before do
- post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
+ create(:cluster, :provided_by_gcp, :project,
+ projects: [project])
+
+ post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
end
- it 'responds with 403' do
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response['message']).to eq('403 Forbidden')
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
+ it 'allows multiple clusters to be associated to project' do
+ expect(project.reload.clusters.count).to eq(2)
end
end
end
diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb
index 471fc99117b..6cf0619cde4 100644
--- a/spec/requests/api/project_container_repositories_spec.rb
+++ b/spec/requests/api/project_container_repositories_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectContainerRepositories do
+RSpec.describe API::ProjectContainerRepositories do
include ExclusiveLeaseHelpers
let_it_be(:project) { create(:project, :private) }
diff --git a/spec/requests/api/project_events_spec.rb b/spec/requests/api/project_events_spec.rb
index f65c62f9402..f3e592f9796 100644
--- a/spec/requests/api/project_events_spec.rb
+++ b/spec/requests/api/project_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectEvents do
+RSpec.describe API::ProjectEvents do
let(:user) { create(:user) }
let(:non_member) { create(:user) }
let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 58034322a13..d7ba3b4e158 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectExport, :clean_gitlab_redis_cache do
+RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do
let_it_be(:project) { create(:project) }
let_it_be(:project_none) { create(:project) }
let_it_be(:project_started) { create(:project) }
@@ -237,7 +237,7 @@ describe API::ProjectExport, :clean_gitlab_redis_cache do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_download_export][:threshold].call + 1)
end
it 'prevents requesting project export' do
@@ -362,7 +362,7 @@ describe API::ProjectExport, :clean_gitlab_redis_cache do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
- .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_export][:threshold] + 1)
+ .and_return(Gitlab::ApplicationRateLimiter.rate_limits[:project_export][:threshold].call + 1)
end
it 'prevents requesting project export' do
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 4474f2f0577..8ab90e26a51 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectHooks, 'ProjectHooks' do
+RSpec.describe API::ProjectHooks, 'ProjectHooks' do
let(:user) { create(:user) }
let(:user3) { create(:user) }
let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 563acd0ece4..a6ae636996e 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectImport do
+RSpec.describe API::ProjectImport do
include WorkhorseHelpers
let(:user) { create(:user) }
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
index c5911d51706..b238949ce47 100644
--- a/spec/requests/api/project_milestones_spec.rb
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectMilestones do
+RSpec.describe API::ProjectMilestones do
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb
new file mode 100644
index 00000000000..0ece3bff8f9
--- /dev/null
+++ b/spec/requests/api/project_packages_spec.rb
@@ -0,0 +1,272 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::ProjectPackages do
+ let(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let!(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
+ let(:package_url) { "/projects/#{project.id}/packages/#{package1.id}" }
+ let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
+ let!(:another_package) { create(:npm_package) }
+ let(:no_package_url) { "/projects/#{project.id}/packages/0" }
+ let(:wrong_package_url) { "/projects/#{project.id}/packages/#{another_package.id}" }
+
+ describe 'GET /projects/:id/packages' do
+ let(:url) { "/projects/#{project.id}/packages" }
+ let(:package_schema) { 'public_api/v4/packages/packages' }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'project is public' do
+ it_behaves_like 'returns packages', :project, :no_type
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ context 'for unauthenticated user' do
+ it_behaves_like 'rejects packages access', :project, :no_type, :not_found
+ end
+
+ context 'for authenticated user' do
+ subject { get api(url, user) }
+
+ it_behaves_like 'returns packages', :project, :maintainer
+ it_behaves_like 'returns packages', :project, :developer
+ it_behaves_like 'returns packages', :project, :reporter
+ it_behaves_like 'rejects packages access', :project, :no_type, :not_found
+ it_behaves_like 'rejects packages access', :project, :guest, :forbidden
+
+ context 'user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns the destroy url' do
+ subject
+
+ expect(json_response.first['_links']).to include('delete_api_path')
+ end
+ end
+ end
+ end
+
+ context 'with pagination params' do
+ let!(:package3) { create(:maven_package, project: project) }
+ let!(:package4) { create(:maven_package, project: project) }
+
+ context 'with pagination params' do
+ let!(:package3) { create(:npm_package, project: project) }
+ let!(:package4) { create(:npm_package, project: project) }
+
+ it_behaves_like 'returns paginated packages'
+ end
+ end
+
+ context 'with sorting' do
+ let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
+
+ before do
+ travel_to(1.day.ago) do
+ package3
+ end
+ end
+
+ it_behaves_like 'package sorting', 'name' do
+ let(:packages) { [package1, package2, package3] }
+ end
+
+ it_behaves_like 'package sorting', 'created_at' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting', 'version' do
+ let(:packages) { [package3, package2, package1] }
+ end
+
+ it_behaves_like 'package sorting', 'type' do
+ let(:packages) { [package3, package1, package2] }
+ end
+ end
+
+ it_behaves_like 'filters on each package_type', is_project: true
+
+ context 'filtering on package_name' do
+ include_context 'package filter context'
+
+ it 'returns the named package' do
+ url = package_filter_url(:name, 'nuget')
+ get api(url, user)
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['name']).to include(package2.name)
+ end
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/packages/:package_id' do
+ subject { get api(package_url, user) }
+
+ shared_examples 'no destroy url' do
+ it 'returns no destroy url' do
+ subject
+
+ expect(json_response['_links']).not_to include('delete_api_path')
+ end
+ end
+
+ shared_examples 'destroy url' do
+ it 'returns destroy url' do
+ subject
+
+ expect(json_response['_links']['delete_api_path']).to be_present
+ end
+ end
+
+ context 'without the need for a license' do
+ context 'project is public' do
+ it 'returns 200 and the package information' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package')
+ end
+
+ it 'returns 404 when the package does not exist' do
+ get api(no_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for the package from a different project' do
+ get api(wrong_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'no destroy url'
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404 for non authenticated user' do
+ get api(package_url)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for a user without access to the project' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns 200 and the package information' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package')
+ end
+
+ it_behaves_like 'no destroy url'
+ end
+
+ context 'user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'destroy url'
+ end
+
+ context 'with pipeline' do
+ let!(:package1) { create(:npm_package, :with_build, project: project) }
+
+ it 'returns the pipeline info' do
+ project.add_developer(user)
+
+ get api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/packages/package_with_build')
+ end
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/packages/:package_id' do
+ context 'without the need for a license' do
+ context 'project is public' do
+ it 'returns 403 for non authenticated user' do
+ delete api(package_url)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns 403 for a user without access to the project' do
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it 'returns 404 for non authenticated user' do
+ delete api(package_url)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for a user without access to the project' do
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 when the package does not exist' do
+ project.add_maintainer(user)
+
+ delete api(no_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 404 for the package from a different project' do
+ project.add_maintainer(user)
+
+ delete api(wrong_package_url, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 403 for a user without enough permissions' do
+ project.add_developer(user)
+
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns 204' do
+ project.add_maintainer(user)
+
+ delete api(package_url, user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb
index 40966e31d0d..4c9e058ef13 100644
--- a/spec/requests/api/project_repository_storage_moves_spec.rb
+++ b/spec/requests/api/project_repository_storage_moves_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectRepositoryStorageMoves do
+RSpec.describe API::ProjectRepositoryStorageMoves do
include AccessMatchersForRequest
let_it_be(:user) { create(:admin) }
diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb
index a54f317782b..f23e374407b 100644
--- a/spec/requests/api/project_snapshots_spec.rb
+++ b/spec/requests/api/project_snapshots_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectSnapshots do
+RSpec.describe API::ProjectSnapshots do
include WorkhorseHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 22189dc3299..fbb0e3e109f 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-describe API::ProjectSnippets do
+RSpec.describe API::ProjectSnippets do
+ include SnippetHelpers
+
let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
@@ -84,19 +86,22 @@ describe API::ProjectSnippets do
end
describe 'GET /projects/:project_id/snippets/:id' do
- let(:user) { create(:user) }
- let(:snippet) { create(:project_snippet, :public, :repository, project: project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:snippet) { create(:project_snippet, :public, :repository, project: project) }
it 'returns snippet json' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
- expect(response).to have_gitlab_http_status(:ok)
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['title']).to eq(snippet.title)
- expect(json_response['description']).to eq(snippet.description)
- expect(json_response['file_name']).to eq(snippet.file_name_on_repo)
- expect(json_response['ssh_url_to_repo']).to eq(snippet.ssh_url_to_repo)
- expect(json_response['http_url_to_repo']).to eq(snippet.http_url_to_repo)
+ expect(json_response['title']).to eq(snippet.title)
+ expect(json_response['description']).to eq(snippet.description)
+ expect(json_response['file_name']).to eq(snippet.file_name_on_repo)
+ expect(json_response['files']).to eq(snippet.blobs.map { |blob| snippet_blob_file(blob) } )
+ expect(json_response['ssh_url_to_repo']).to eq(snippet.ssh_url_to_repo)
+ expect(json_response['http_url_to_repo']).to eq(snippet.http_url_to_repo)
+ end
end
it 'returns 404 for invalid snippet id' do
@@ -111,6 +116,10 @@ describe API::ProjectSnippets do
let(:request) { get api("/projects/#{project_no_snippets.id}/snippets/123", user) }
end
end
+
+ it_behaves_like 'snippet_multiple_files feature disabled' do
+ subject { get api("/projects/#{project.id}/snippets/#{snippet.id}", user) }
+ end
end
describe 'POST /projects/:project_id/snippets/' do
@@ -443,7 +452,7 @@ describe API::ProjectSnippets do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'text/plain'
+ expect(response.media_type).to eq 'text/plain'
end
it 'returns 404 for invalid snippet id' do
@@ -465,4 +474,12 @@ describe API::ProjectSnippets do
subject { get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", snippet.author) }
end
end
+
+ describe 'GET /projects/:project_id/snippets/:id/files/:ref/:file_path/raw' do
+ let_it_be(:snippet) { create(:project_snippet, :repository, author: admin, project: project) }
+
+ it_behaves_like 'raw snippet files' do
+ let(:api_path) { "/projects/#{snippet.project.id}/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" }
+ end
+ end
end
diff --git a/spec/requests/api/project_statistics_spec.rb b/spec/requests/api/project_statistics_spec.rb
index 89809a97b96..5f0cac403aa 100644
--- a/spec/requests/api/project_statistics_spec.rb
+++ b/spec/requests/api/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectStatistics do
+RSpec.describe API::ProjectStatistics do
let_it_be(:developer) { create(:user) }
let_it_be(:public_project) { create(:project, :public) }
diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb
index caeb465080e..59b2b09f0bf 100644
--- a/spec/requests/api/project_templates_spec.rb
+++ b/spec/requests/api/project_templates_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProjectTemplates do
+RSpec.describe API::ProjectTemplates do
let_it_be(:public_project) { create(:project, :public, path: 'path.with.dot') }
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:developer) { create(:user) }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index c3f29ec47a9..76b0c04e32d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-shared_examples 'languages and percentages JSON response' do
+RSpec.shared_examples 'languages and percentages JSON response' do
let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }
before do
@@ -46,7 +46,7 @@ shared_examples 'languages and percentages JSON response' do
end
end
-describe API::Projects do
+RSpec.describe API::Projects do
include ProjectForksHelper
let(:user) { create(:user) }
@@ -254,7 +254,10 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.first).to include 'statistics'
+
+ statistics = json_response.first['statistics']
+ expect(statistics).to be_present
+ expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size')
end
it "does not include license by default" do
@@ -584,6 +587,85 @@ describe API::Projects do
end
end
+ context 'sorting by project statistics' do
+ %w(repository_size storage_size wiki_size).each do |order_by|
+ context "sorting by #{order_by}" do
+ before do
+ ProjectStatistics.update_all(order_by => 100)
+ project4.statistics.update_columns(order_by => 10)
+ project.statistics.update_columns(order_by => 200)
+ end
+
+ context 'admin user' do
+ let(:current_user) { admin }
+
+ context "when sorting by #{order_by} ascendingly" do
+ it 'returns a properly sorted list of projects' do
+ get api('/projects', current_user), params: { order_by: order_by, sort: :asc }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(project4.id)
+ end
+ end
+
+ context "when sorting by #{order_by} descendingly" do
+ it 'returns a properly sorted list of projects' do
+ get api('/projects', current_user), params: { order_by: order_by, sort: :desc }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(project.id)
+ end
+ end
+ end
+
+ context 'non-admin user' do
+ let(:current_user) { user }
+ let(:projects) { [public_project, project, project2, project3] }
+
+ it 'returns projects ordered normally' do
+ get api('/projects', current_user), params: { order_by: order_by }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to eq(projects.map(&:id).reverse)
+ end
+ end
+ end
+ end
+ end
+
+ context 'filtering by repository_storage' do
+ before do
+ [project, project3].each { |proj| proj.update_columns(repository_storage: 'nfs-11') }
+ # Since we don't actually have Gitaly configured with an nfs-11 storage, an error would be raised
+ # when we present the projects in a response, as we ask Gitaly for stuff like default branch and Gitaly
+ # is not configured for a nfs-11 storage. So we trick Rails into thinking the storage for these projects
+ # is still default (in reality, it is).
+ allow_any_instance_of(Project).to receive(:repository_storage).and_return('default')
+ end
+
+ context 'admin user' do
+ it_behaves_like 'projects response' do
+ let(:filter) { { repository_storage: 'nfs-11' } }
+ let(:current_user) { admin }
+ let(:projects) { [project, project3] }
+ end
+ end
+
+ context 'non-admin user' do
+ it_behaves_like 'projects response' do
+ let(:filter) { { repository_storage: 'nfs-11' } }
+ let(:current_user) { user }
+ let(:projects) { [public_project, project, project2, project3] }
+ end
+ end
+ end
+
context 'with keyset pagination' do
let(:current_user) { user }
let(:projects) { [public_project, project, project2, project3] }
@@ -1846,6 +1928,13 @@ describe API::Projects do
end
end
end
+
+ it 'exposes service desk attributes' do
+ get api("/projects/#{project.id}", user)
+
+ expect(json_response).to have_key 'service_desk_enabled'
+ expect(json_response).to have_key 'service_desk_address'
+ end
end
describe 'GET /projects/:id/users' do
@@ -2133,7 +2222,7 @@ describe API::Projects do
expect(json_response['expires_at']).to eq(expires_at.to_s)
end
- it 'updates project authorization' do
+ it 'updates project authorization', :sidekiq_inline do
expect do
post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }
end.to(
@@ -2590,6 +2679,26 @@ describe API::Projects do
end
end
end
+
+ context 'when updating service desk' do
+ subject { put(api("/projects/#{project.id}", user), params: { service_desk_enabled: true }) }
+
+ before do
+ project.update!(service_desk_enabled: false)
+
+ allow(::Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
+ end
+
+ it 'returns 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'enables the service_desk' do
+ expect { subject }.to change { project.reload.service_desk_enabled }.to(true)
+ end
+ end
end
describe 'POST /projects/:id/archive' do
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index 9203e0ec819..8bcd493eb1f 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProtectedBranches do
+RSpec.describe API::ProtectedBranches do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:protected_name) { 'feature' }
diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb
index 3bc8ecbee73..cc7261dafc9 100644
--- a/spec/requests/api/protected_tags_spec.rb
+++ b/spec/requests/api/protected_tags_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ProtectedTags do
+RSpec.describe API::ProtectedTags do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository) }
let(:project2) { create(:project, path: 'project2', namespace: user.namespace) }
diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb
new file mode 100644
index 00000000000..b4e83c8caab
--- /dev/null
+++ b/spec/requests/api/pypi_packages_spec.rb
@@ -0,0 +1,259 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::PypiPackages do
+ include WorkhorseHelpers
+ include PackagesManagerApiSpecHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public) }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do
+ let_it_be(:package) { create(:pypi_package, project: project) }
+ let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | true | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :developer | true | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | true | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :developer | false | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | false | true | 'PyPi package versions' | :success
+ 'PUBLIC' | :developer | false | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :guest | false | false | 'PyPi package versions' | :success
+ 'PUBLIC' | :anonymous | false | true | 'PyPi package versions' | :success
+ 'PRIVATE' | :developer | true | true | 'PyPi package versions' | :success
+ 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package GET requests'
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+
+ describe 'POST /api/v4/projects/:id/packages/pypi/authorize' do
+ let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:url) { "/projects/#{project.id}/packages/pypi/authorize" }
+ let(:headers) { {} }
+
+ subject { post api(url), headers: headers }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'process PyPi api request' | :success
+ 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :success
+ 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+
+ describe 'POST /api/v4/projects/:id/packages/pypi' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let_it_be(:file_name) { 'package.whl' }
+ let(:url) { "/projects/#{project.id}/packages/pypi" }
+ let(:headers) { {} }
+ let(:base_params) { { requires_python: '>=3.7', version: '1.0.0', name: 'sample-project', sha256_digest: '123' } }
+ let(:params) { base_params.merge(content: temp_file(file_name)) }
+ let(:send_rewritten_field) { true }
+
+ subject do
+ workhorse_finalize(
+ api(url),
+ method: :post,
+ file_key: :content,
+ params: params,
+ headers: headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'PyPi package creation' | :created
+ 'PUBLIC' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :developer | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :guest | false | true | 'process PyPi api request' | :forbidden
+ 'PUBLIC' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PUBLIC' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | true | true | 'process PyPi api request' | :created
+ 'PRIVATE' | :guest | true | true | 'process PyPi api request' | :forbidden
+ 'PRIVATE' | :developer | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | true | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :developer | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :guest | false | true | 'process PyPi api request' | :not_found
+ 'PRIVATE' | :developer | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :guest | false | false | 'process PyPi api request' | :unauthorized
+ 'PRIVATE' | :anonymous | false | true | 'process PyPi api request' | :unauthorized
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:user_headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ context 'with an invalid package' do
+ let(:token) { personal_access_token.token }
+ let(:user_headers) { build_basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_header) }
+
+ before do
+ params[:name] = '.$/@!^*'
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ it_behaves_like 'deploy token for package uploads'
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
+ let_it_be(:package_name) { 'Dummy-Package' }
+ let_it_be(:package) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') }
+
+ let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
+
+ subject { get api(url) }
+
+ context 'without the need for a license' do
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
+ 'PUBLIC' | :developer | true | true | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | true | true | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | true | false | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | true | false | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | false | true | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | false | true | 'PyPi package download' | :success
+ 'PUBLIC' | :developer | false | false | 'PyPi package download' | :success
+ 'PUBLIC' | :guest | false | false | 'PyPi package download' | :success
+ 'PUBLIC' | :anonymous | false | true | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | true | true | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | true | true | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | true | false | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | true | false | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | false | true | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | false | true | 'PyPi package download' | :success
+ 'PRIVATE' | :developer | false | false | 'PyPi package download' | :success
+ 'PRIVATE' | :guest | false | false | 'PyPi package download' | :success
+ 'PRIVATE' | :anonymous | false | true | 'PyPi package download' | :success
+ end
+
+ with_them do
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
+ end
+ end
+
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header('foo', 'bar') }
+
+ it_behaves_like 'returning response status', :success
+ end
+ end
+
+ it_behaves_like 'rejects PyPI access with unknown project id'
+ end
+ end
+end
diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb
index cf2043ecc74..82d0d64eba4 100644
--- a/spec/requests/api/release/links_spec.rb
+++ b/spec/requests/api/release/links_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Release::Links do
+RSpec.describe API::Release::Links do
let(:project) { create(:project, :repository, :private) }
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index f4cb7f25990..5e8353d74c3 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Releases do
+RSpec.describe API::Releases do
let(:project) { create(:project, :repository, :private) }
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
diff --git a/spec/requests/api/remote_mirrors_spec.rb b/spec/requests/api/remote_mirrors_spec.rb
index 3029b8443b0..436efb708fd 100644
--- a/spec/requests/api/remote_mirrors_spec.rb
+++ b/spec/requests/api/remote_mirrors_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::RemoteMirrors do
+RSpec.describe API::RemoteMirrors do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :remote_mirror) }
let_it_be(:developer) { create(:user) { |u| project.add_developer(u) } }
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 55243e83017..36707f32d04 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require 'mime/types'
-describe API::Repositories do
+RSpec.describe API::Repositories do
include RepoHelpers
include WorkhorseHelpers
@@ -227,7 +227,8 @@ describe API::Repositories do
end
describe "GET /projects/:id/repository/archive(.:format)?:sha" do
- let(:route) { "/projects/#{project.id}/repository/archive" }
+ let(:project_id) { CGI.escape(project.full_path) }
+ let(:route) { "/projects/#{project_id}/repository/archive" }
before do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
@@ -246,7 +247,7 @@ describe API::Repositories do
end
it 'returns the repository archive archive.zip' do
- get api("/projects/#{project.id}/repository/archive.zip", user)
+ get api("/projects/#{project_id}/repository/archive.zip", user)
expect(response).to have_gitlab_http_status(:ok)
@@ -257,7 +258,7 @@ describe API::Repositories do
end
it 'returns the repository archive archive.tar.bz2' do
- get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
+ get api("/projects/#{project_id}/repository/archive.tar.bz2", user)
expect(response).to have_gitlab_http_status(:ok)
@@ -277,7 +278,7 @@ describe API::Repositories do
it 'rate limits user when thresholds hit' do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true)
- get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
+ get api("/projects/#{project_id}/repository/archive.tar.bz2", user)
expect(response).to have_gitlab_http_status(:too_many_requests)
end
@@ -302,6 +303,13 @@ describe API::Repositories do
end
end
+ context 'when unauthenticated and project path has dots' do
+ it_behaves_like 'repository archive' do
+ let(:project) { create(:project, :public, :repository, path: 'path.with.dot') }
+ let(:current_user) { nil }
+ end
+ end
+
context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do
let(:request) { get api(route) }
diff --git a/spec/requests/api/resource_label_events_spec.rb b/spec/requests/api/resource_label_events_spec.rb
index 7619399458a..a4a70d89812 100644
--- a/spec/requests/api/resource_label_events_spec.rb
+++ b/spec/requests/api/resource_label_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ResourceLabelEvents do
+RSpec.describe API::ResourceLabelEvents do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public, namespace: user.namespace) }
let_it_be(:label) { create(:label, project: project) }
diff --git a/spec/requests/api/resource_milestone_events_spec.rb b/spec/requests/api/resource_milestone_events_spec.rb
index b2e92fde5ee..5c81c2180d7 100644
--- a/spec/requests/api/resource_milestone_events_spec.rb
+++ b/spec/requests/api/resource_milestone_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::ResourceMilestoneEvents do
+RSpec.describe API::ResourceMilestoneEvents do
let!(:user) { create(:user) }
let!(:project) { create(:project, :public, namespace: user.namespace) }
let!(:milestone) { create(:milestone, project: project) }
diff --git a/spec/requests/api/resource_state_events_spec.rb b/spec/requests/api/resource_state_events_spec.rb
new file mode 100644
index 00000000000..46ca9874395
--- /dev/null
+++ b/spec/requests/api/resource_state_events_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::ResourceStateEvents do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: user.namespace) }
+
+ before_all do
+ project.add_developer(user)
+ end
+
+ shared_examples 'resource_state_events API' do |parent_type, eventable_type, id_name|
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_state_events" do
+ let!(:event) { create_event }
+
+ it "returns an array of resource state events" do
+ url = "/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events"
+ get api(url, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(event.id)
+ expect(json_response.first['state']).to eq(event.state.to_s)
+ end
+
+ it "returns a 404 error when eventable id not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{non_existing_record_id}/resource_state_events", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns 404 when not authorized" do
+ parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ private_user = create(:user)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events", private_user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_state_events/:event_id" do
+ let!(:event) { create_event }
+
+ it "returns a resource state event by id" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events/#{event.id}", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['id']).to eq(event.id)
+ expect(json_response['state']).to eq(event.state.to_s)
+ end
+
+ it "returns 404 when not authorized" do
+ parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ private_user = create(:user)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events/#{event.id}", private_user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it "returns a 404 error if resource state event not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events/#{non_existing_record_id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe 'pagination' do
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/220192
+ it 'returns the second page' do
+ create_event
+ event2 = create_event
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_state_events?page=2&per_page=1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq '2'
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['id']).to eq(event2.id)
+ end
+ end
+
+ def create_event(state: :opened)
+ create(:resource_state_event, eventable.class.name.underscore => eventable, state: state)
+ end
+ end
+
+ context 'when eventable is an Issue' do
+ it_behaves_like 'resource_state_events API', 'projects', 'issues', 'iid' do
+ let(:parent) { project }
+ let(:eventable) { create(:issue, project: project, author: user) }
+ end
+ end
+
+ context 'when eventable is a Merge Request' do
+ it_behaves_like 'resource_state_events API', 'projects', 'merge_requests', 'iid' do
+ let(:parent) { project }
+ let(:eventable) { create(:merge_request, source_project: project, target_project: project, author: user) }
+ end
+ end
+end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index a02d804ee9b..1a93be98a67 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Search do
+RSpec.describe API::Search do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :wiki_repo, :public, name: 'awesome project', group: group) }
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 53265574e6a..5528a0c094f 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe API::Services do
+RSpec.describe API::Services do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index e6dd1fecb69..602aacb6ced 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Settings, 'Settings' do
+RSpec.describe API::Settings, 'Settings' do
let(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
@@ -62,14 +62,14 @@ describe API::Settings, 'Settings' do
default_projects_limit: 3,
default_project_creation: 2,
password_authentication_enabled_for_web: false,
- repository_storages: ['custom'],
+ repository_storages: 'custom',
plantuml_enabled: true,
plantuml_url: 'http://plantuml.example.com',
sourcegraph_enabled: true,
sourcegraph_url: 'https://sourcegraph.com',
sourcegraph_public_only: false,
default_snippet_visibility: 'internal',
- restricted_visibility_levels: ['public'],
+ restricted_visibility_levels: 'public',
default_artifacts_expire_in: '2 days',
help_page_text: 'custom help text',
help_page_hide_commercial_content: true,
@@ -94,7 +94,9 @@ describe API::Settings, 'Settings' do
issues_create_limit: 300,
raw_blob_request_limit: 300,
spam_check_endpoint_enabled: true,
- spam_check_endpoint_url: 'https://example.com/spam_check'
+ spam_check_endpoint_url: 'https://example.com/spam_check',
+ disabled_oauth_sign_in_sources: 'unknown',
+ import_sources: 'github,bitbucket'
}
expect(response).to have_gitlab_http_status(:ok)
@@ -135,6 +137,8 @@ describe API::Settings, 'Settings' do
expect(json_response['raw_blob_request_limit']).to eq(300)
expect(json_response['spam_check_endpoint_enabled']).to be_truthy
expect(json_response['spam_check_endpoint_url']).to eq('https://example.com/spam_check')
+ expect(json_response['disabled_oauth_sign_in_sources']).to eq([])
+ expect(json_response['import_sources']).to match_array(%w(github bitbucket))
end
end
diff --git a/spec/requests/api/sidekiq_metrics_spec.rb b/spec/requests/api/sidekiq_metrics_spec.rb
index 705ae29d5d8..23ac2ea5c0b 100644
--- a/spec/requests/api/sidekiq_metrics_spec.rb
+++ b/spec/requests/api/sidekiq_metrics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::SidekiqMetrics do
+RSpec.describe API::SidekiqMetrics do
let(:admin) { create(:user, :admin) }
describe 'GET sidekiq/*' do
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index c12c95ae2e0..e676eb94337 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -2,14 +2,16 @@
require 'spec_helper'
-describe API::Snippets do
+RSpec.describe API::Snippets do
+ include SnippetHelpers
+
let_it_be(:user) { create(:user) }
describe 'GET /snippets/' do
it 'returns snippets available' do
- public_snippet = create(:personal_snippet, :public, author: user)
- private_snippet = create(:personal_snippet, :private, author: user)
- internal_snippet = create(:personal_snippet, :internal, author: user)
+ public_snippet = create(:personal_snippet, :repository, :public, author: user)
+ private_snippet = create(:personal_snippet, :repository, :private, author: user)
+ internal_snippet = create(:personal_snippet, :repository, :internal, author: user)
get api("/snippets/", user)
@@ -22,6 +24,7 @@ describe API::Snippets do
private_snippet.id)
expect(json_response.last).to have_key('web_url')
expect(json_response.last).to have_key('raw_url')
+ expect(json_response.last).to have_key('files')
expect(json_response.last).to have_key('visibility')
end
@@ -59,32 +62,33 @@ describe API::Snippets do
end
describe 'GET /snippets/public' do
- let!(:other_user) { create(:user) }
- let!(:public_snippet) { create(:personal_snippet, :public, author: user) }
- let!(:private_snippet) { create(:personal_snippet, :private, author: user) }
- let!(:internal_snippet) { create(:personal_snippet, :internal, author: user) }
- let!(:public_snippet_other) { create(:personal_snippet, :public, author: other_user) }
- let!(:private_snippet_other) { create(:personal_snippet, :private, author: other_user) }
- let!(:internal_snippet_other) { create(:personal_snippet, :internal, author: other_user) }
- let!(:public_snippet_project) { create(:project_snippet, :public, author: user) }
- let!(:private_snippet_project) { create(:project_snippet, :private, author: user) }
- let!(:internal_snippet_project) { create(:project_snippet, :internal, author: user) }
+ let_it_be(:other_user) { create(:user) }
+ let_it_be(:public_snippet) { create(:personal_snippet, :repository, :public, author: user) }
+ let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: user) }
+ let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: user) }
+ let_it_be(:public_snippet_other) { create(:personal_snippet, :repository, :public, author: other_user) }
+ let_it_be(:private_snippet_other) { create(:personal_snippet, :repository, :private, author: other_user) }
+ let_it_be(:internal_snippet_other) { create(:personal_snippet, :repository, :internal, author: other_user) }
+ let_it_be(:public_snippet_project) { create(:project_snippet, :repository, :public, author: user) }
+ let_it_be(:private_snippet_project) { create(:project_snippet, :repository, :private, author: user) }
+ let_it_be(:internal_snippet_project) { create(:project_snippet, :repository, :internal, author: user) }
it 'returns all snippets with public visibility from all users' do
get api("/snippets/public", user)
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
- public_snippet.id,
- public_snippet_other.id)
- expect(json_response.map { |snippet| snippet['web_url']} ).to contain_exactly(
- "http://localhost/snippets/#{public_snippet.id}",
- "http://localhost/snippets/#{public_snippet_other.id}")
- expect(json_response.map { |snippet| snippet['raw_url']} ).to contain_exactly(
- "http://localhost/snippets/#{public_snippet.id}/raw",
- "http://localhost/snippets/#{public_snippet_other.id}/raw")
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
+ public_snippet.id,
+ public_snippet_other.id)
+ expect(json_response.map { |snippet| snippet['web_url']} ).to contain_exactly(
+ "http://localhost/snippets/#{public_snippet.id}",
+ "http://localhost/snippets/#{public_snippet_other.id}")
+ expect(json_response[0]['files'].first).to eq snippet_blob_file(public_snippet_other.blobs.first)
+ expect(json_response[1]['files'].first).to eq snippet_blob_file(public_snippet.blobs.first)
+ end
end
end
@@ -102,13 +106,8 @@ describe API::Snippets do
get api("/snippets/#{snippet.id}/raw", author)
expect(response).to have_gitlab_http_status(:ok)
- expect(response.content_type).to eq 'text/plain'
- end
-
- it 'forces attachment content disposition' do
- get api("/snippets/#{snippet.id}/raw", author)
-
- expect(headers['Content-Disposition']).to match(/^attachment/)
+ expect(response.media_type).to eq 'text/plain'
+ expect(headers['Content-Disposition']).to match(/^inline/)
end
it 'returns 404 for invalid snippet id' do
@@ -141,56 +140,88 @@ describe API::Snippets do
end
end
+ describe 'GET /snippets/:id/files/:ref/:file_path/raw' do
+ let_it_be(:snippet) { create(:personal_snippet, :repository, :private) }
+
+ it_behaves_like 'raw snippet files' do
+ let(:api_path) { "/snippets/#{snippet_id}/files/#{ref}/#{file_path}/raw" }
+ end
+ end
+
describe 'GET /snippets/:id' do
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:author) { create(:user) }
let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: author) }
let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: author) }
+ let(:snippet) { private_snippet }
- it 'requires authentication' do
- get api("/snippets/#{private_snippet.id}", nil)
+ subject { get api("/snippets/#{snippet.id}", user) }
- expect(response).to have_gitlab_http_status(:unauthorized)
+ it 'hides private snippets from an ordinary user' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
end
- it 'returns snippet json' do
- get api("/snippets/#{private_snippet.id}", author)
+ context 'without a user' do
+ let(:user) { nil }
- expect(response).to have_gitlab_http_status(:ok)
+ it 'requires authentication' do
+ subject
- expect(json_response['title']).to eq(private_snippet.title)
- expect(json_response['description']).to eq(private_snippet.description)
- expect(json_response['file_name']).to eq(private_snippet.file_name_on_repo)
- expect(json_response['visibility']).to eq(private_snippet.visibility)
- expect(json_response['ssh_url_to_repo']).to eq(private_snippet.ssh_url_to_repo)
- expect(json_response['http_url_to_repo']).to eq(private_snippet.http_url_to_repo)
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
end
- it 'shows private snippets to an admin' do
- get api("/snippets/#{private_snippet.id}", admin)
+ context 'with the author' do
+ let(:user) { author }
- expect(response).to have_gitlab_http_status(:ok)
- end
+ it 'returns snippet json' do
+ subject
- it 'hides private snippets from an ordinary user' do
- get api("/snippets/#{private_snippet.id}", user)
+ expect(response).to have_gitlab_http_status(:ok)
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['title']).to eq(private_snippet.title)
+ expect(json_response['description']).to eq(private_snippet.description)
+ expect(json_response['file_name']).to eq(private_snippet.file_name_on_repo)
+ expect(json_response['files']).to eq(private_snippet.blobs.map { |blob| snippet_blob_file(blob) })
+ expect(json_response['visibility']).to eq(private_snippet.visibility)
+ expect(json_response['ssh_url_to_repo']).to eq(private_snippet.ssh_url_to_repo)
+ expect(json_response['http_url_to_repo']).to eq(private_snippet.http_url_to_repo)
+ end
end
- it 'shows internal snippets to an ordinary user' do
- get api("/snippets/#{internal_snippet.id}", user)
+ context 'with an admin' do
+ let(:user) { admin }
- expect(response).to have_gitlab_http_status(:ok)
+ it 'shows private snippets to an admin' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'returns 404 for invalid snippet id' do
+ private_snippet.destroy
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Snippet Not Found')
+ end
end
- it 'returns 404 for invalid snippet id' do
- private_snippet.destroy
+ context 'with an internal snippet' do
+ let(:snippet) { internal_snippet }
- get api("/snippets/#{private_snippet.id}", admin)
+ it 'shows internal snippets to an ordinary user' do
+ subject
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq('404 Snippet Not Found')
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it_behaves_like 'snippet_multiple_files feature disabled' do
+ let(:user) { author }
end
end
@@ -221,6 +252,7 @@ describe API::Snippets do
expect(json_response['title']).to eq(params[:title])
expect(json_response['description']).to eq(params[:description])
expect(json_response['file_name']).to eq(params[:file_name])
+ expect(json_response['files']).to eq(snippet.blobs.map { |blob| snippet_blob_file(blob) })
expect(json_response['visibility']).to eq(params[:visibility])
end
@@ -251,6 +283,10 @@ describe API::Snippets do
it_behaves_like 'snippet creation'
+ it_behaves_like 'snippet_multiple_files feature disabled' do
+ let(:snippet) { Snippet.find(json_response["id"]) }
+ end
+
context 'with an external user' do
let(:user) { create(:user, :external) }
diff --git a/spec/requests/api/statistics_spec.rb b/spec/requests/api/statistics_spec.rb
index 5aea5c225a0..eab97b6916e 100644
--- a/spec/requests/api/statistics_spec.rb
+++ b/spec/requests/api/statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Statistics, 'Statistics' do
+RSpec.describe API::Statistics, 'Statistics' do
include ProjectForksHelper
tables_to_analyze = %w[
projects
diff --git a/spec/requests/api/submodules_spec.rb b/spec/requests/api/submodules_spec.rb
index 2604dc18005..6b141d6d036 100644
--- a/spec/requests/api/submodules_spec.rb
+++ b/spec/requests/api/submodules_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Submodules do
+RSpec.describe API::Submodules do
let(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace ) }
let(:guest) { create(:user) { |u| project.add_guest(u) } }
diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb
index ffb8c811622..34d3c54d700 100644
--- a/spec/requests/api/suggestions_spec.rb
+++ b/spec/requests/api/suggestions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Suggestions do
+RSpec.describe API::Suggestions do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 609aa615d33..01b46053d52 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::SystemHooks do
+RSpec.describe API::SystemHooks do
include StubRequests
let(:user) { create(:user) }
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 694802ce1b8..b029c0f5793 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Tags do
+RSpec.describe API::Tags do
let(:user) { create(:user) }
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb
index 4dd1e27bd4b..97ce858ba12 100644
--- a/spec/requests/api/task_completion_status_spec.rb
+++ b/spec/requests/api/task_completion_status_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'task completion status response' do
+RSpec.describe 'task completion status response' do
let_it_be(:user) { create(:user) }
let_it_be(:project) do
create(:project, :public, creator_id: user.id, namespace: user.namespace)
diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb
index fae338b4ca3..e1c5bfd82c4 100644
--- a/spec/requests/api/templates_spec.rb
+++ b/spec/requests/api/templates_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Templates do
+RSpec.describe API::Templates do
context 'the Template Entity' do
before do
get api('/templates/gitignores/Ruby')
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index ec9db5566e3..c6cba39314b 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Terraform::State do
+RSpec.describe API::Terraform::State do
include HttpBasicAuthHelpers
let_it_be(:project) { create(:project) }
@@ -59,10 +59,11 @@ describe API::Terraform::State do
context 'with developer permissions' do
let(:current_user) { developer }
- it 'returns forbidden if the user cannot access the state' do
+ it 'returns terraform state belonging to a project of given state name' do
request
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(state.file.read)
end
end
end
@@ -94,10 +95,11 @@ describe API::Terraform::State do
context 'with developer permissions' do
let(:job) { create(:ci_build, project: project, user: developer) }
- it 'returns forbidden if the user cannot access the state' do
+ it 'returns terraform state belonging to a project of given state name' do
request
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(state.file.read)
end
end
end
@@ -235,9 +237,43 @@ describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'state is already locked' do
+ before do
+ state.update!(lock_xid: 'locked', locked_by_user: current_user)
+ end
+
+ it 'returns an error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'user does not have permission to lock the state' do
+ let(:current_user) { developer }
+
+ it 'returns an error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
end
describe 'DELETE /projects/:id/terraform/state/:name/lock' do
+ let(:params) do
+ {
+ ID: lock_id,
+ Version: '0.1',
+ Operation: 'OperationTypePlan',
+ Info: '',
+ Who: "#{current_user.username}",
+ Created: Time.now.utc.iso8601(6),
+ Path: ''
+ }
+ end
+
before do
state.lock_xid = '123-456'
state.save!
@@ -246,7 +282,7 @@ describe API::Terraform::State do
subject(:request) { delete api("#{state_path}/lock"), headers: auth_header, params: params }
context 'with the correct lock id' do
- let(:params) { { ID: '123-456' } }
+ let(:lock_id) { '123-456' }
it 'removes the terraform state lock' do
request
@@ -266,7 +302,7 @@ describe API::Terraform::State do
end
context 'with an incorrect lock id' do
- let(:params) { { ID: '456-789' } }
+ let(:lock_id) { '456-789' }
it 'returns an error' do
request
@@ -276,7 +312,7 @@ describe API::Terraform::State do
end
context 'with a longer than 255 character lock id' do
- let(:params) { { ID: '0' * 256 } }
+ let(:lock_id) { '0' * 256 }
it 'returns an error' do
request
@@ -284,5 +320,16 @@ describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:bad_request)
end
end
+
+ context 'user does not have permission to unlock the state' do
+ let(:lock_id) { '123-456' }
+ let(:current_user) { developer }
+
+ it 'returns an error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
end
end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 0bdc71a30e9..dfd0e13d84c 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Todos do
+RSpec.describe API::Todos do
let_it_be(:group) { create(:group) }
let_it_be(:project_1) { create(:project, :repository, group: group) }
let_it_be(:project_2) { create(:project) }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 19b01cb7913..c51358bf659 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Triggers do
+RSpec.describe API::Triggers do
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb
index 688dfe11115..94e25d647fc 100644
--- a/spec/requests/api/user_counts_spec.rb
+++ b/spec/requests/api/user_counts_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::UserCounts do
+RSpec.describe API::UserCounts do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index e780f67bcab..17f9112c1d5 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Users, :do_not_mock_admin_mode do
+RSpec.describe API::Users, :do_not_mock_admin_mode do
let_it_be(:admin) { create(:admin) }
let_it_be(:user, reload: true) { create(:user, username: 'user.with.dot') }
let_it_be(:key) { create(:key, user: user) }
@@ -910,6 +910,14 @@ describe API::Users, :do_not_mock_admin_mode do
expect(user.reload.bio).to eq('')
end
+ it 'updates user with nil bio' do
+ put api("/users/#{user.id}", admin), params: { bio: nil }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['bio']).to eq('')
+ expect(user.reload.bio).to eq('')
+ end
+
it "updates user with new password and forces reset on next login" do
put api("/users/#{user.id}", admin), params: { password: '12345678' }
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index f209a1d2e6e..7bb73e9664b 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Variables do
+RSpec.describe API::Variables do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
@@ -54,6 +54,59 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when there are two variables with the same key on different env' do
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
+
+ context 'when filter[environment_scope] is not passed' do
+ context 'FF ci_variables_api_filter_environment_scope is enabled' do
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'FF ci_variables_api_filter_environment_scope is disabled' do
+ before do
+ stub_feature_flags(ci_variables_api_filter_environment_scope: false)
+ end
+
+ it 'returns random one' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['key']).to eq('key1')
+ end
+ end
+ end
+
+ context 'when filter[environment_scope] is passed' do
+ it 'returns the variable' do
+ get api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['value']).to eq(var2.value)
+ end
+ end
+
+ context 'when wrong filter[environment_scope] is passed' do
+ it 'returns not_found' do
+ get api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'when there is only one variable with provided key' do
+ it 'returns not_found' do
+ get api("/projects/#{project.id}/variables/#{variable.key}", user), params: { 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
end
context 'authorized user with invalid permissions' do
@@ -173,6 +226,52 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when there are two variables with the same key on different env' do
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
+
+ context 'when filter[environment_scope] is not passed' do
+ context 'FF ci_variables_api_filter_environment_scope is enabled' do
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'FF ci_variables_api_filter_environment_scope is disabled' do
+ before do
+ stub_feature_flags(ci_variables_api_filter_environment_scope: false)
+ end
+
+ it 'updates random one' do
+ put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['value']).to eq('new_val')
+ end
+ end
+ end
+
+ context 'when filter[environment_scope] is passed' do
+ it 'updates the variable' do
+ put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val', 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(var1.reload.value).not_to eq('new_val')
+ expect(var2.reload.value).to eq('new_val')
+ end
+ end
+
+ context 'when wrong filter[environment_scope] is passed' do
+ it 'returns not_found' do
+ put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val', 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
context 'authorized user with invalid permissions' do
@@ -207,6 +306,56 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when there are two variables with the same key on different env' do
+ let!(:var1) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'staging') }
+ let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
+
+ context 'when filter[environment_scope] is not passed' do
+ context 'FF ci_variables_api_filter_environment_scope is enabled' do
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
+
+ expect(response).to have_gitlab_http_status(:conflict)
+ end
+ end
+
+ context 'FF ci_variables_api_filter_environment_scope is disabled' do
+ before do
+ stub_feature_flags(ci_variables_api_filter_environment_scope: false)
+ end
+
+ it 'deletes random one' do
+ expect do
+ delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change {project.variables.count}.by(-1)
+ end
+ end
+ end
+
+ context 'when filter[environment_scope] is passed' do
+ it 'deletes the variable' do
+ expect do
+ delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change {project.variables.count}.by(-1)
+
+ expect(var1.reload).to be_present
+ expect { var2.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'when wrong filter[environment_scope] is passed' do
+ it 'returns not_found' do
+ delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'invalid' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
context 'authorized user with invalid permissions' do
diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb
index 9eb8c03e273..a0a0f66c8d1 100644
--- a/spec/requests/api/version_spec.rb
+++ b/spec/requests/api/version_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe API::Version do
+RSpec.describe API::Version do
shared_examples_for 'GET /version' do
context 'when unauthenticated' do
it 'returns authentication error' do
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index 43a5cb446bb..f271f8aa853 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -12,7 +12,7 @@ require 'spec_helper'
# - maintainer
# because they are 3 edge cases of using wiki pages.
-describe API::Wikis do
+RSpec.describe API::Wikis do
include WorkhorseHelpers
let(:user) { create(:user) }
@@ -21,178 +21,10 @@ describe API::Wikis do
let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
let(:expected_keys_with_content) { %w(content format slug title) }
let(:expected_keys_without_content) { %w(format slug title) }
+ let(:wiki) { project_wiki }
- shared_examples_for 'returns list of wiki pages' do
- context 'when wiki has pages' do
- let!(:pages) do
- [create(:wiki_page, wiki: project_wiki, title: 'page1', content: 'content of page1'),
- create(:wiki_page, wiki: project_wiki, title: 'page2.with.dot', content: 'content of page2')]
- end
-
- it 'returns the list of wiki pages without content' do
- get api(url, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(2)
-
- json_response.each_with_index do |page, index|
- expect(page.keys).to match_array(expected_keys_without_content)
- expect(page['slug']).to eq(pages[index].slug)
- expect(page['title']).to eq(pages[index].title)
- end
- end
-
- it 'returns the list of wiki pages with content' do
- get api(url, user), params: { with_content: 1 }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(2)
-
- json_response.each_with_index do |page, index|
- expect(page.keys).to match_array(expected_keys_with_content)
- expect(page['content']).to eq(pages[index].content)
- expect(page['slug']).to eq(pages[index].slug)
- expect(page['title']).to eq(pages[index].title)
- end
- end
- end
-
- it 'return the empty list of wiki pages' do
- get api(url, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(0)
- end
- end
-
- shared_examples_for 'returns wiki page' do
- it 'returns the wiki page' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(page.content)
- expect(json_response['slug']).to eq(page.slug)
- expect(json_response['title']).to eq(page.title)
- end
- end
-
- shared_examples_for 'creates wiki page' do
- it 'creates the wiki page' do
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(payload[:content])
- expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
- expect(json_response['title']).to eq(payload[:title])
- expect(json_response['rdoc']).to eq(payload[:rdoc])
- end
-
- [:title, :content].each do |part|
- it "responds with validation error on empty #{part}" do
- payload.delete(part)
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response.size).to eq(1)
- expect(json_response['error']).to eq("#{part} is missing")
- end
- end
- end
-
- shared_examples_for 'updates wiki page' do
- it 'updates the wiki page' do
- put(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.size).to eq(4)
- expect(json_response.keys).to match_array(expected_keys_with_content)
- expect(json_response['content']).to eq(payload[:content])
- expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
- expect(json_response['title']).to eq(payload[:title])
- end
-
- [:title, :content, :format].each do |part|
- it "updates with wiki with missing #{part}" do
- payload.delete(part)
-
- put(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
- shared_examples_for '403 Forbidden' do
- it 'returns 403 Forbidden' do
- expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response.size).to eq(1)
- expect(json_response['message']).to eq('403 Forbidden')
- end
- end
-
- shared_examples_for '404 Wiki Page Not Found' do
- it 'returns 404 Wiki Page Not Found' do
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response.size).to eq(1)
- expect(json_response['message']).to eq('404 Wiki Page Not Found')
- end
- end
-
- shared_examples_for '404 Project Not Found' do
- it 'returns 404 Project Not Found' do
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response.size).to eq(1)
- expect(json_response['message']).to eq('404 Project Not Found')
- end
- end
-
- shared_examples_for '204 No Content' do
- it 'returns 204 No Content' do
- expect(response).to have_gitlab_http_status(:no_content)
- end
- end
-
- shared_examples_for 'uploads wiki attachment' do
- it 'pushes attachment to the wiki repository' do
- allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
-
- workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq result_hash.deep_stringify_keys
- end
-
- it 'responds with validation error on empty file' do
- payload.delete(:file)
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response.size).to eq(1)
- expect(json_response['error']).to eq('file is missing')
- end
-
- it 'responds with validation error on invalid temp file' do
- payload[:file] = { tempfile: '/etc/hosts' }
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response.size).to eq(1)
- expect(json_response['error']).to eq('file is invalid')
- end
-
- it 'is backward compatible with regular multipart uploads' do
- allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
-
- post(api(url, user), params: payload)
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response).to eq result_hash.deep_stringify_keys
- end
+ shared_examples_for 'wiki API 404 Project Not Found' do
+ include_examples 'wiki API 404 Not Found', 'Project'
end
describe 'GET /projects/:id/wikis' do
@@ -206,7 +38,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -216,7 +48,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -226,7 +58,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -238,7 +70,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -246,7 +78,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
context 'when user is maintainer' do
@@ -254,7 +86,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
end
@@ -266,7 +98,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -274,7 +106,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
context 'when user is maintainer' do
@@ -282,7 +114,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'returns list of wiki pages'
+ include_examples 'wikis API returns list of wiki pages'
end
end
end
@@ -299,7 +131,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -309,7 +141,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -319,7 +151,7 @@ describe API::Wikis do
get api(url, user)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -331,7 +163,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -340,12 +172,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -356,12 +188,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -374,7 +206,7 @@ describe API::Wikis do
get api(url)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -384,12 +216,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -400,12 +232,12 @@ describe API::Wikis do
get api(url, user)
end
- include_examples 'returns wiki page'
+ include_examples 'wikis API returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -423,7 +255,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -432,7 +264,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -441,7 +273,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -453,7 +285,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -461,7 +293,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
context 'when user is maintainer' do
@@ -469,7 +301,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
end
@@ -481,7 +313,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -489,7 +321,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
context 'when user is maintainer' do
@@ -497,7 +329,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'creates wiki page'
+ include_examples 'wikis API creates wiki page'
end
end
end
@@ -515,7 +347,7 @@ describe API::Wikis do
put(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -525,7 +357,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -535,7 +367,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -547,7 +379,7 @@ describe API::Wikis do
put(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -555,7 +387,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -564,7 +396,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -573,7 +405,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -582,7 +414,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -595,7 +427,7 @@ describe API::Wikis do
put(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -603,7 +435,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -612,7 +444,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -621,7 +453,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
@@ -630,7 +462,7 @@ describe API::Wikis do
put(api(url, user), params: payload)
end
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -638,7 +470,7 @@ describe API::Wikis do
context 'when wiki belongs to a group project' do
let(:project) { create(:project, :wiki_repo, namespace: group) }
- include_examples 'updates wiki page'
+ include_examples 'wikis API updates wiki page'
end
end
@@ -654,7 +486,7 @@ describe API::Wikis do
delete(api(url))
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -664,7 +496,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -674,7 +506,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -686,7 +518,7 @@ describe API::Wikis do
delete(api(url))
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -696,7 +528,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -706,7 +538,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '204 No Content'
+ include_examples 'wiki API 204 No Content'
end
end
@@ -718,7 +550,7 @@ describe API::Wikis do
delete(api(url))
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -728,7 +560,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -738,12 +570,12 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '204 No Content'
+ include_examples 'wiki API 204 No Content'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
- include_examples '404 Wiki Page Not Found'
+ include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
@@ -755,7 +587,7 @@ describe API::Wikis do
delete(api(url, user))
end
- include_examples '204 No Content'
+ include_examples 'wiki API 204 No Content'
end
end
@@ -783,7 +615,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -792,7 +624,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
@@ -801,7 +633,7 @@ describe API::Wikis do
post(api(url, user), params: payload)
end
- include_examples '403 Forbidden'
+ include_examples 'wiki API 403 Forbidden'
end
end
@@ -813,7 +645,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -821,7 +653,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
context 'when user is maintainer' do
@@ -829,7 +661,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
end
@@ -841,7 +673,7 @@ describe API::Wikis do
post(api(url), params: payload)
end
- include_examples '404 Project Not Found'
+ include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
@@ -849,7 +681,7 @@ describe API::Wikis do
project.add_developer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
context 'when user is maintainer' do
@@ -857,7 +689,7 @@ describe API::Wikis do
project.add_maintainer(user)
end
- include_examples 'uploads wiki attachment'
+ include_examples 'wiki API uploads wiki attachment'
end
end
end
diff --git a/spec/requests/boards/lists_controller_spec.rb b/spec/requests/boards/lists_controller_spec.rb
index 7451ad93efd..4d9f1dace4d 100644
--- a/spec/requests/boards/lists_controller_spec.rb
+++ b/spec/requests/boards/lists_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Boards::ListsController do
+RSpec.describe Boards::ListsController do
describe '#index' do
let(:board) { create(:board) }
let(:user) { board.project.owner }
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 183a3545cf2..d387c6df4cf 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Git HTTP requests' do
+RSpec.describe 'Git HTTP requests' do
include ProjectForksHelper
include TermsHelper
include GitHttpHelpers
diff --git a/spec/requests/groups/milestones_controller_spec.rb b/spec/requests/groups/milestones_controller_spec.rb
index 1c6743dc678..2c4d97ec4c3 100644
--- a/spec/requests/groups/milestones_controller_spec.rb
+++ b/spec/requests/groups/milestones_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::MilestonesController do
+RSpec.describe Groups::MilestonesController do
context 'N+1 DB queries' do
let(:user) { create(:user) }
let!(:public_group) { create(:group, :public) }
diff --git a/spec/requests/groups/registry/repositories_controller_spec.rb b/spec/requests/groups/registry/repositories_controller_spec.rb
index ab59b006be7..89cbd3e4100 100644
--- a/spec/requests/groups/registry/repositories_controller_spec.rb
+++ b/spec/requests/groups/registry/repositories_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Groups::Registry::RepositoriesController do
+RSpec.describe Groups::Registry::RepositoriesController do
let_it_be(:group, reload: true) { create(:group) }
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/health_controller_spec.rb b/spec/requests/health_controller_spec.rb
index 6ee716e0e89..592a57bc637 100644
--- a/spec/requests/health_controller_spec.rb
+++ b/spec/requests/health_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe HealthController do
+RSpec.describe HealthController do
include StubENV
let(:token) { Gitlab::CurrentSettings.health_check_access_token }
@@ -129,6 +129,40 @@ describe HealthController do
expect(response).to have_gitlab_http_status(:service_unavailable)
expect(response.headers['X-GitLab-Custom-Error']).to eq(1)
end
+
+ context 'when DB is not accessible and connection raises an exception' do
+ before do
+ expect(Gitlab::HealthChecks::DbCheck)
+ .to receive(:readiness)
+ .and_raise(PG::ConnectionBad, 'could not connect to server')
+ end
+
+ it 'responds with 500 including the exception info' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:internal_server_error)
+ expect(response.headers['X-GitLab-Custom-Error']).to eq(1)
+ expect(json_response).to eq(
+ { 'status' => 'failed', 'message' => 'PG::ConnectionBad : could not connect to server' })
+ end
+ end
+
+ context 'when any exception happens during the probing' do
+ before do
+ expect(Gitlab::HealthChecks::Redis::RedisCheck)
+ .to receive(:readiness)
+ .and_raise(::Redis::CannotConnectError, 'Redis down')
+ end
+
+ it 'responds with 500 including the exception info' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:internal_server_error)
+ expect(response.headers['X-GitLab-Custom-Error']).to eq(1)
+ expect(json_response).to eq(
+ { 'status' => 'failed', 'message' => 'Redis::CannotConnectError : Redis down' })
+ end
+ end
end
end
diff --git a/spec/requests/import/gitlab_groups_controller_spec.rb b/spec/requests/import/gitlab_groups_controller_spec.rb
index 35f2bf0c2f7..4125c5c7c7a 100644
--- a/spec/requests/import/gitlab_groups_controller_spec.rb
+++ b/spec/requests/import/gitlab_groups_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GitlabGroupsController do
+RSpec.describe Import::GitlabGroupsController do
include WorkhorseHelpers
let(:import_path) { "#{Dir.tmpdir}/gitlab_groups_controller_spec" }
@@ -16,6 +16,8 @@ describe Import::GitlabGroupsController do
expect(import_export).to receive(:storage_path).and_return(import_path)
end
+ allow(Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
+
stub_uploads_object_storage(ImportExportUploader)
end
diff --git a/spec/requests/import/gitlab_projects_controller_spec.rb b/spec/requests/import/gitlab_projects_controller_spec.rb
index f16755e9766..c1ac5a9f2c8 100644
--- a/spec/requests/import/gitlab_projects_controller_spec.rb
+++ b/spec/requests/import/gitlab_projects_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Import::GitlabProjectsController do
+RSpec.describe Import::GitlabProjectsController do
include WorkhorseHelpers
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 617587e2fa6..fe6c0f0a556 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe JwtController do
+RSpec.describe JwtController do
include_context 'parsed logs'
let(:service) { double(execute: {}) }
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index f3fa5e36fec..f7771c7b0f9 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe 'Git LFS API and storage' do
+RSpec.describe 'Git LFS API and storage' do
include LfsHttpHelpers
include ProjectForksHelper
include WorkhorseHelpers
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index 41cf1a80205..34e345cb1cf 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Git LFS File Locking API' do
+RSpec.describe 'Git LFS File Locking API' do
include WorkhorseHelpers
let(:project) { create(:project) }
diff --git a/spec/requests/oauth_tokens_spec.rb b/spec/requests/oauth_tokens_spec.rb
index bb1c25d686e..6d944bbc783 100644
--- a/spec/requests/oauth_tokens_spec.rb
+++ b/spec/requests/oauth_tokens_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OAuth Tokens requests' do
+RSpec.describe 'OAuth Tokens requests' do
let(:user) { create :user }
let(:application) { create :oauth_application, scopes: 'api' }
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 785ab98a3d0..7b682d76150 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'OpenID Connect requests' do
+RSpec.describe 'OpenID Connect requests' do
let(:user) do
create(
:user,
@@ -146,8 +146,16 @@ describe 'OpenID Connect requests' do
expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i
end
+ it 'has public email in email claim' do
+ expect(@payload['email']).to eq(user.public_email)
+ end
+
+ it 'has true in email_verified claim' do
+ expect(@payload['email_verified']).to eq(true)
+ end
+
it 'does not include any unknown properties' do
- expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time sub_legacy]
+ expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time sub_legacy email email_verified]
end
end
@@ -211,5 +219,20 @@ describe 'OpenID Connect requests' do
expect(json_response['email_verified']).to eq(true)
end
end
+
+ context 'ID token payload' do
+ before do
+ request_access_token!
+ @payload = JSON::JWT.decode(json_response['id_token'], :skip_verification)
+ end
+
+ it 'has private email in email claim' do
+ expect(@payload['email']).to eq(user.email)
+ end
+
+ it 'has true in email_verified claim' do
+ expect(@payload['email_verified']).to eq(true)
+ end
+ end
end
end
diff --git a/spec/requests/product_analytics/collector_app_attack_spec.rb b/spec/requests/product_analytics/collector_app_attack_spec.rb
new file mode 100644
index 00000000000..6f86e39c295
--- /dev/null
+++ b/spec/requests/product_analytics/collector_app_attack_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ProductAnalytics::CollectorApp throttle' do
+ include RackAttackSpecHelpers
+
+ include_context 'rack attack cache store'
+
+ let(:project1) { create(:project) }
+ let(:project2) { create(:project) }
+
+ before do
+ allow(ProductAnalyticsEvent).to receive(:create).and_return(true)
+ end
+
+ context 'per application id' do
+ let(:params) do
+ {
+ aid: project1.id,
+ eid: SecureRandom.uuid
+ }
+ end
+
+ it 'throttles the endpoint' do
+ # Allow requests under the rate limit.
+ 100.times do
+ expect_ok { get '/-/collector/i', params: params }
+ end
+
+ # Ensure its not related to ip address
+ random_next_ip
+
+ # Reject request over the limit
+ expect_rejection { get '/-/collector/i', params: params }
+
+ # But allows request for different aid
+ expect_ok { get '/-/collector/i', params: params.merge(aid: project2.id) }
+ end
+ end
+end
diff --git a/spec/requests/product_analytics/collector_app_spec.rb b/spec/requests/product_analytics/collector_app_spec.rb
new file mode 100644
index 00000000000..0491c2564f0
--- /dev/null
+++ b/spec/requests/product_analytics/collector_app_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ProductAnalytics::CollectorApp' do
+ let_it_be(:project) { create(:project) }
+ let(:params) { {} }
+
+ subject { get '/-/collector/i', params: params }
+
+ RSpec.shared_examples 'not found' do
+ it 'repond with 404' do
+ expect { subject }.not_to change { ProductAnalyticsEvent.count }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'correct event params' do
+ let(:params) do
+ {
+ aid: project.id,
+ p: 'web',
+ tna: 'sp',
+ tv: 'js-2.14.0',
+ eid: SecureRandom.uuid,
+ duid: SecureRandom.uuid,
+ sid: SecureRandom.uuid,
+ vid: 4,
+ url: 'http://example.com/products/1',
+ refr: 'http://example.com/products/1',
+ lang: 'en-US',
+ cookie: true,
+ tz: 'America/Los_Angeles',
+ cs: 'UTF-8'
+ }
+ end
+
+ it 'repond with 200' do
+ expect { subject }.to change { ProductAnalyticsEvent.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'feature disabled' do
+ before do
+ stub_feature_flags(product_analytics: false)
+ end
+
+ it_behaves_like 'not found'
+ end
+ end
+
+ context 'empty event params' do
+ it_behaves_like 'not found'
+ end
+
+ context 'invalid project id in params' do
+ let(:params) do
+ {
+ aid: '-1',
+ p: 'web',
+ tna: 'sp',
+ tv: 'js-2.14.0',
+ eid: SecureRandom.uuid,
+ duid: SecureRandom.uuid,
+ sid: SecureRandom.uuid
+ }
+ end
+
+ it_behaves_like 'not found'
+ end
+end
diff --git a/spec/requests/profiles/notifications_controller_spec.rb b/spec/requests/profiles/notifications_controller_spec.rb
index 0b2741677ab..d60cee00aef 100644
--- a/spec/requests/profiles/notifications_controller_spec.rb
+++ b/spec/requests/profiles/notifications_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'view user notifications' do
+RSpec.describe 'view user notifications' do
let(:user) do
create(:user) do |user|
user.emails.create(email: 'original@example.com', confirmed_at: Time.current)
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index 773f243e733..8c3058d405c 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'value stream analytics events' do
+RSpec.describe 'value stream analytics events' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, public_builds: false) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
diff --git a/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb
new file mode 100644
index 00000000000..c246aacb4c7
--- /dev/null
+++ b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'PagerDuty webhook' do
+ let_it_be(:project) { create(:project) }
+
+ describe 'POST /incidents/pagerduty' do
+ let(:payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
+ let(:webhook_processor_class) { ::IncidentManagement::PagerDuty::ProcessWebhookService }
+ let(:webhook_processor) { instance_double(webhook_processor_class) }
+
+ def make_request
+ headers = { 'Content-Type' => 'application/json' }
+ post project_incidents_pagerduty_url(project, token: 'VALID-TOKEN'), params: payload.to_json, headers: headers
+ end
+
+ before do
+ allow(webhook_processor_class).to receive(:new).and_return(webhook_processor)
+ allow(webhook_processor).to receive(:execute).and_return(ServiceResponse.success(http_status: :accepted))
+ end
+
+ it 'calls PagerDuty webhook processor with correct parameters' do
+ make_request
+
+ expect(webhook_processor_class).to have_received(:new).with(project, nil, payload)
+ expect(webhook_processor).to have_received(:execute).with('VALID-TOKEN')
+ end
+
+ it 'responds with 202 Accepted' do
+ make_request
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+ end
+end
diff --git a/spec/requests/projects/merge_requests/creations_spec.rb b/spec/requests/projects/merge_requests/creations_spec.rb
index d192e1bca7f..0a3e663444f 100644
--- a/spec/requests/projects/merge_requests/creations_spec.rb
+++ b/spec/requests/projects/merge_requests/creations_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'merge requests creations' do
+RSpec.describe 'merge requests creations' do
describe 'GET /:namespace/:project/merge_requests/new' do
include ProjectForksHelper
diff --git a/spec/requests/projects/merge_requests_discussions_spec.rb b/spec/requests/projects/merge_requests_discussions_spec.rb
index 94e9c81bc3b..6ec586ed22c 100644
--- a/spec/requests/projects/merge_requests_discussions_spec.rb
+++ b/spec/requests/projects/merge_requests_discussions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'merge requests discussions' do
+RSpec.describe 'merge requests discussions' do
# Further tests can be found at merge_requests_controller_spec.rb
describe 'GET /:namespace/:project/-/merge_requests/:iid/discussions' do
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/projects/metrics_dashboard_spec.rb b/spec/requests/projects/metrics_dashboard_spec.rb
new file mode 100644
index 00000000000..ab35788387c
--- /dev/null
+++ b/spec/requests/projects/metrics_dashboard_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'metrics dashboard page' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:environment2) { create(:environment, project: project) }
+ let_it_be(:user) { project.owner }
+
+ before do
+ project.add_developer(user)
+ login_as(user)
+ end
+
+ describe 'GET /:namespace/:project/-/metrics' do
+ it 'returns 200' do
+ send_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns environment' do
+ send_request
+ expect(assigns(:environment).id).to eq(environment.id)
+ end
+ end
+
+ describe 'GET /:namespace/:project/-/metrics?environment=:environment.id' do
+ it 'returns 200' do
+ send_request(environment: environment2.id)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns query param environment' do
+ send_request(environment: environment2.id)
+ expect(assigns(:environment).id).to eq(environment2.id)
+ end
+
+ context 'when query param environment does not exist' do
+ it 'responds with 404' do
+ send_request(environment: 99)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET /:namespace/:project/-/metrics/:dashboard_path' do
+ let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' }
+
+ it 'returns 200' do
+ send_request(dashboard_path: dashboard_path)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns environment' do
+ send_request(dashboard_path: dashboard_path)
+ expect(assigns(:environment).id).to eq(environment.id)
+ end
+ end
+
+ describe 'GET :/namespace/:project/-/metrics/:dashboard_path?environment=:environment.id' do
+ let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' }
+
+ it 'returns 200' do
+ send_request(dahboard_path: dashboard_path, environment: environment.id)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'assigns query param environment' do
+ send_request(dashboard_path: dashboard_path, environment: environment2.id)
+ expect(assigns(:environment).id).to eq(environment2.id)
+ end
+
+ context 'when query param environment does not exist' do
+ it 'responds with 404' do
+ send_request(dashboard_path: dashboard_path, environment: 99)
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ def send_request(params = {})
+ get namespace_project_metrics_dashboard_path(namespace_id: project.namespace, project_id: project, **params)
+ end
+end
diff --git a/spec/requests/projects/uploads_spec.rb b/spec/requests/projects/uploads_spec.rb
index aca4644289d..de5ef36be7e 100644
--- a/spec/requests/projects/uploads_spec.rb
+++ b/spec/requests/projects/uploads_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'File uploads' do
+RSpec.describe 'File uploads' do
include WorkhorseHelpers
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index 175c5dd0088..106515a6b13 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Rack Attack global throttles' do
+RSpec.describe 'Rack Attack global throttles' do
include RackAttackSpecHelpers
let(:settings) { Gitlab::CurrentSettings.current_application_settings }
diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb
index 36ccfc6b400..7f9999bf3d2 100644
--- a/spec/requests/request_profiler_spec.rb
+++ b/spec/requests/request_profiler_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Request Profiler' do
+RSpec.describe 'Request Profiler' do
let(:user) { create(:user) }
shared_examples 'profiling a request' do |profile_type, extension|
diff --git a/spec/requests/self_monitoring_project_spec.rb b/spec/requests/self_monitoring_project_spec.rb
index 6a0258c349f..5844a27da17 100644
--- a/spec/requests/self_monitoring_project_spec.rb
+++ b/spec/requests/self_monitoring_project_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Self-Monitoring project requests' do
+RSpec.describe 'Self-Monitoring project requests' do
let(:admin) { create(:admin) }
describe 'POST #create_self_monitoring_project' do
diff --git a/spec/requests/sessions_spec.rb b/spec/requests/sessions_spec.rb
index 6697700c37d..95df181b7b0 100644
--- a/spec/requests/sessions_spec.rb
+++ b/spec/requests/sessions_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Sessions' do
+RSpec.describe 'Sessions' do
context 'authentication', :allow_forgery_protection do
let(:user) { create(:user) }
diff --git a/spec/requests/user_activity_spec.rb b/spec/requests/user_activity_spec.rb
index b24760d475b..6f0726dbdc9 100644
--- a/spec/requests/user_activity_spec.rb
+++ b/spec/requests/user_activity_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Update of user activity' do
+RSpec.describe 'Update of user activity' do
let(:user) { create(:user, last_activity_on: nil) }
before do
diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb
index 66c7ce4d533..1397741af18 100644
--- a/spec/requests/user_avatar_spec.rb
+++ b/spec/requests/user_avatar_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'Loading a user avatar' do
+RSpec.describe 'Loading a user avatar' do
let(:user) { create(:user, :with_avatar) }
context 'when logged in' do
diff --git a/spec/requests/user_spoofs_ip_spec.rb b/spec/requests/user_spoofs_ip_spec.rb
index 8da15665132..833dae78529 100644
--- a/spec/requests/user_spoofs_ip_spec.rb
+++ b/spec/requests/user_spoofs_ip_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'User spoofs their IP' do
+RSpec.describe 'User spoofs their IP' do
it 'raises a 400 error' do
get '/nonexistent', headers: { 'Client-Ip' => '1.2.3.4', 'X-Forwarded-For' => '5.6.7.8' }