diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 17:22:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 17:22:11 +0300 |
commit | 0c872e02b2c822e3397515ec324051ff540f0cd5 (patch) | |
tree | ce2fb6ce7030e4dad0f4118d21ab6453e5938cdd /spec/requests | |
parent | f7e05a6853b12f02911494c4b3fe53d9540d74fc (diff) |
Add latest changes from gitlab-org/gitlab@15-7-stable-eev15.7.0-rc42
Diffstat (limited to 'spec/requests')
583 files changed, 4578 insertions, 2282 deletions
diff --git a/spec/requests/abuse_reports_controller_spec.rb b/spec/requests/abuse_reports_controller_spec.rb index 94c80ccb89a..510855d95e0 100644 --- a/spec/requests/abuse_reports_controller_spec.rb +++ b/spec/requests/abuse_reports_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AbuseReportsController do +RSpec.describe AbuseReportsController, feature_category: :users do let(:reporter) { create(:user) } let(:user) { create(:user) } let(:attrs) do diff --git a/spec/requests/admin/applications_controller_spec.rb b/spec/requests/admin/applications_controller_spec.rb index 03553757080..c83137ebbce 100644 --- a/spec/requests/admin/applications_controller_spec.rb +++ b/spec/requests/admin/applications_controller_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Admin::ApplicationsController, :enable_admin_mode do +RSpec.describe Admin::ApplicationsController, :enable_admin_mode, +feature_category: :authentication_and_authorization do let_it_be(:admin) { create(:admin) } let_it_be(:application) { create(:oauth_application, owner_id: nil, owner_type: nil) } let_it_be(:show_path) { admin_application_path(application) } diff --git a/spec/requests/admin/background_migrations_controller_spec.rb b/spec/requests/admin/background_migrations_controller_spec.rb index fe2a2470511..db3e2fa0df6 100644 --- a/spec/requests/admin/background_migrations_controller_spec.rb +++ b/spec/requests/admin/background_migrations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::BackgroundMigrationsController, :enable_admin_mode do +RSpec.describe Admin::BackgroundMigrationsController, :enable_admin_mode, feature_category: :database do let(:admin) { create(:admin) } before do diff --git a/spec/requests/admin/batched_jobs_controller_spec.rb b/spec/requests/admin/batched_jobs_controller_spec.rb index fb51b3bce88..8060b19b8b3 100644 --- a/spec/requests/admin/batched_jobs_controller_spec.rb +++ b/spec/requests/admin/batched_jobs_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::BatchedJobsController, :enable_admin_mode do +RSpec.describe Admin::BatchedJobsController, :enable_admin_mode, feature_category: :database do let(:admin) { create(:admin) } before do diff --git a/spec/requests/admin/broadcast_messages_controller_spec.rb b/spec/requests/admin/broadcast_messages_controller_spec.rb index eb29092845c..69b84d6d795 100644 --- a/spec/requests/admin/broadcast_messages_controller_spec.rb +++ b/spec/requests/admin/broadcast_messages_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::BroadcastMessagesController, :enable_admin_mode do +RSpec.describe Admin::BroadcastMessagesController, :enable_admin_mode, feature_category: :onboarding do let(:broadcast_message) { build(:broadcast_message) } let(:broadcast_message_params) { broadcast_message.as_json(root: true, only: [:message, :starts_at, :ends_at]) } diff --git a/spec/requests/admin/clusters/integrations_controller_spec.rb b/spec/requests/admin/clusters/integrations_controller_spec.rb index ee1c1d5aad4..d5e3f03627a 100644 --- a/spec/requests/admin/clusters/integrations_controller_spec.rb +++ b/spec/requests/admin/clusters/integrations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::Clusters::IntegrationsController, :enable_admin_mode do +RSpec.describe Admin::Clusters::IntegrationsController, :enable_admin_mode, feature_category: :integrations do include AccessMatchersForController shared_examples 'a secure endpoint' do diff --git a/spec/requests/admin/hook_logs_controller_spec.rb b/spec/requests/admin/hook_logs_controller_spec.rb index f8d3381c052..fa9f317dbba 100644 --- a/spec/requests/admin/hook_logs_controller_spec.rb +++ b/spec/requests/admin/hook_logs_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::HookLogsController, :enable_admin_mode do +RSpec.describe Admin::HookLogsController, :enable_admin_mode, feature_category: :integrations do let_it_be(:user) { create(:admin) } let_it_be_with_refind(:web_hook) { create(:system_hook) } let_it_be_with_refind(:web_hook_log) { create(:web_hook_log, web_hook: web_hook) } diff --git a/spec/requests/admin/impersonation_tokens_controller_spec.rb b/spec/requests/admin/impersonation_tokens_controller_spec.rb index ee0e12ad0c0..15212db0e77 100644 --- a/spec/requests/admin/impersonation_tokens_controller_spec.rb +++ b/spec/requests/admin/impersonation_tokens_controller_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe Admin::ImpersonationTokensController, :enable_admin_mode do +RSpec.describe Admin::ImpersonationTokensController, :enable_admin_mode, +feature_category: :authentication_and_authorization do let(:admin) { create(:admin) } let!(:user) { create(:user) } diff --git a/spec/requests/admin/integrations_controller_spec.rb b/spec/requests/admin/integrations_controller_spec.rb index 128aada0975..efd0e3d91ee 100644 --- a/spec/requests/admin/integrations_controller_spec.rb +++ b/spec/requests/admin/integrations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::IntegrationsController, :enable_admin_mode do +RSpec.describe Admin::IntegrationsController, :enable_admin_mode, feature_category: :integrations do let_it_be(:admin) { create(:admin) } before do diff --git a/spec/requests/admin/version_check_controller_spec.rb b/spec/requests/admin/version_check_controller_spec.rb index 7e2f33d5bc5..47221bf37e5 100644 --- a/spec/requests/admin/version_check_controller_spec.rb +++ b/spec/requests/admin/version_check_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Admin::VersionCheckController, :enable_admin_mode do +RSpec.describe Admin::VersionCheckController, :enable_admin_mode, feature_category: :not_owned do let(:admin) { create(:admin) } before do diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index 2af6c438fc9..8c14ead9e42 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::AccessRequests do +RSpec.describe API::AccessRequests, feature_category: :authentication_and_authorization 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/batched_background_migrations_spec.rb b/spec/requests/api/admin/batched_background_migrations_spec.rb index 3b396a91d3e..9712777d261 100644 --- a/spec/requests/api/admin/batched_background_migrations_spec.rb +++ b/spec/requests/api/admin/batched_background_migrations_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Admin::BatchedBackgroundMigrations do +RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :database do let(:admin) { create(:admin) } let(:unauthorized_user) { create(:user) } diff --git a/spec/requests/api/admin/instance_clusters_spec.rb b/spec/requests/api/admin/instance_clusters_spec.rb index 7b3224f58c5..7b510f74fd4 100644 --- a/spec/requests/api/admin/instance_clusters_spec.rb +++ b/spec/requests/api/admin/instance_clusters_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::API::Admin::InstanceClusters do +RSpec.describe ::API::Admin::InstanceClusters, feature_category: :kubernetes_management do include KubernetesHelpers let_it_be(:regular_user) { create(:user) } diff --git a/spec/requests/api/admin/plan_limits_spec.rb b/spec/requests/api/admin/plan_limits_spec.rb index 74ea3b0973f..2de7a66d803 100644 --- a/spec/requests/api/admin/plan_limits_spec.rb +++ b/spec/requests/api/admin/plan_limits_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do +RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :not_owned do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:admin) } let_it_be(:plan) { create(:plan, name: 'default') } @@ -40,6 +40,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do expect(json_response['pypi_max_file_size']).to eq(Plan.default.actual_limits.pypi_max_file_size) expect(json_response['terraform_module_max_file_size']).to eq(Plan.default.actual_limits.terraform_module_max_file_size) expect(json_response['storage_size_limit']).to eq(Plan.default.actual_limits.storage_size_limit) + expect(json_response['pipeline_hierarchy_size']).to eq(Plan.default.actual_limits.pipeline_hierarchy_size) end end @@ -70,6 +71,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do expect(json_response['pypi_max_file_size']).to eq(Plan.default.actual_limits.pypi_max_file_size) expect(json_response['terraform_module_max_file_size']).to eq(Plan.default.actual_limits.terraform_module_max_file_size) expect(json_response['storage_size_limit']).to eq(Plan.default.actual_limits.storage_size_limit) + expect(json_response['pipeline_hierarchy_size']).to eq(Plan.default.actual_limits.pipeline_hierarchy_size) end end @@ -118,7 +120,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do 'nuget_max_file_size': 50, 'pypi_max_file_size': 60, 'terraform_module_max_file_size': 70, - 'storage_size_limit': 80 + 'storage_size_limit': 80, + 'pipeline_hierarchy_size': 250 } expect(response).to have_gitlab_http_status(:ok) @@ -140,6 +143,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do expect(json_response['pypi_max_file_size']).to eq(60) expect(json_response['terraform_module_max_file_size']).to eq(70) expect(json_response['storage_size_limit']).to eq(80) + expect(json_response['pipeline_hierarchy_size']).to eq(250) end it 'updates single plan limits' do @@ -183,7 +187,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do 'nuget_max_file_size': 'e', 'pypi_max_file_size': 'f', 'terraform_module_max_file_size': 'g', - 'storage_size_limit': 'j' + 'storage_size_limit': 'j', + 'pipeline_hierarchy_size': 'r' } expect(response).to have_gitlab_http_status(:bad_request) @@ -204,7 +209,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do 'nuget_max_file_size is invalid', 'pypi_max_file_size is invalid', 'terraform_module_max_file_size is invalid', - 'storage_size_limit is invalid' + 'storage_size_limit is invalid', + 'pipeline_hierarchy_size is invalid' ) end end diff --git a/spec/requests/api/admin/sidekiq_spec.rb b/spec/requests/api/admin/sidekiq_spec.rb index 1e626c90e7e..0b456721d4f 100644 --- a/spec/requests/api/admin/sidekiq_spec.rb +++ b/spec/requests/api/admin/sidekiq_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues do +RSpec.describe API::Admin::Sidekiq, :clean_gitlab_redis_queues, feature_category: :not_owned do let_it_be(:admin) { create(:admin) } describe 'DELETE /admin/sidekiq/queues/:queue_name' do diff --git a/spec/requests/api/alert_management_alerts_spec.rb b/spec/requests/api/alert_management_alerts_spec.rb index 680a3883387..8dd0c46eab6 100644 --- a/spec/requests/api/alert_management_alerts_spec.rb +++ b/spec/requests/api/alert_management_alerts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::AlertManagementAlerts do +RSpec.describe API::AlertManagementAlerts, feature_category: :incident_management do let_it_be(:creator) { create(:user) } let_it_be(:project) do create(:project, :public, creator_id: creator.id, namespace: creator.namespace) 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 ba7a01a2cd9..21f3691c20b 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' -RSpec.describe API::APIGuard::AdminModeMiddleware, :request_store do +RSpec.describe API::APIGuard::AdminModeMiddleware, :request_store, feature_category: :not_owned do let(:user) { create(:admin) } it 'is loaded' do diff --git a/spec/requests/api/api_guard/response_coercer_middleware_spec.rb b/spec/requests/api/api_guard/response_coercer_middleware_spec.rb index 6f3f97fe846..77498c2e2b3 100644 --- a/spec/requests/api/api_guard/response_coercer_middleware_spec.rb +++ b/spec/requests/api/api_guard/response_coercer_middleware_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::APIGuard::ResponseCoercerMiddleware do +RSpec.describe API::APIGuard::ResponseCoercerMiddleware, feature_category: :not_owned do using RSpec::Parameterized::TableSyntax it 'is loaded' do diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb index 260f7cbc226..9cf9c313f11 100644 --- a/spec/requests/api/api_spec.rb +++ b/spec/requests/api/api_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::API do +RSpec.describe API::API, feature_category: :authentication_and_authorization do include GroupAPIHelpers describe 'Record user last activity in after hook' do diff --git a/spec/requests/api/appearance_spec.rb b/spec/requests/api/appearance_spec.rb index 69176e18d2e..84d5b091b8d 100644 --- a/spec/requests/api/appearance_spec.rb +++ b/spec/requests/api/appearance_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Appearance, 'Appearance' do +RSpec.describe API::Appearance, 'Appearance', feature_category: :navigation do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:admin) } @@ -33,6 +33,7 @@ RSpec.describe API::Appearance, 'Appearance' do expect(json_response['new_project_guidelines']).to eq('') expect(json_response['profile_image_guidelines']).to eq('') expect(json_response['title']).to eq('') + expect(json_response['short_title']).to eq('') end end end @@ -51,6 +52,7 @@ RSpec.describe API::Appearance, 'Appearance' do it "allows updating the settings" do put api("/application/appearance", admin), params: { title: "GitLab Test Instance", + short_title: "GitLab", description: "gitlab-test.example.com", new_project_guidelines: "Please read the FAQs for help.", profile_image_guidelines: "Custom profile image guidelines" @@ -70,6 +72,7 @@ RSpec.describe API::Appearance, 'Appearance' do expect(json_response['new_project_guidelines']).to eq('Please read the FAQs for help.') expect(json_response['profile_image_guidelines']).to eq('Custom profile image guidelines') expect(json_response['title']).to eq('GitLab Test Instance') + expect(json_response['short_title']).to eq('GitLab') end end diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb index 022451553ee..e238a1fb554 100644 --- a/spec/requests/api/applications_spec.rb +++ b/spec/requests/api/applications_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Applications, :api do +RSpec.describe API::Applications, :api, feature_category: :authentication_and_authorization do let(:admin_user) { create(:user, admin: true) } let(:user) { create(:user, admin: false) } let(:scopes) { 'api' } diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb index 656a086e550..8affbe6ec2b 100644 --- a/spec/requests/api/avatar_spec.rb +++ b/spec/requests/api/avatar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Avatar do +RSpec.describe API::Avatar, feature_category: :users 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 bb563f93bfe..87dc06b7d15 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::AwardEmoji do +RSpec.describe API::AwardEmoji, feature_category: :not_owned do let_it_be_with_reload(:project) { create(:project, :private) } let_it_be(:user) { create(:user) } 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 d8a345a79b0..6c6a7cc7cc6 100644 --- a/spec/requests/api/badges_spec.rb +++ b/spec/requests/api/badges_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Badges do +RSpec.describe API::Badges, feature_category: :projects 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 4d7256a1f03..69804c2c4a4 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Boards do +RSpec.describe API::Boards, feature_category: :team_planning 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 750b9a39e15..eba1a06b5e4 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Branches do +RSpec.describe API::Branches, feature_category: :source_code_management do let_it_be(:user) { create(:user) } let(:project) { create(:project, :repository, creator: user, path: 'my.project', create_branch: 'ends-with.txt') } @@ -344,6 +344,18 @@ RSpec.describe API::Branches do end end + context 'when branch is ambiguous' do + let(:branch_name) { 'prefix' } + + before do + project.repository.create_branch('prefix/branch') + end + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + end + end + context 'when repository does not exist' do it_behaves_like '404 response' do let(:project) { create(:project, creator: user) } diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb index 76412c80f4c..5cbb7dbfa12 100644 --- a/spec/requests/api/broadcast_messages_spec.rb +++ b/spec/requests/api/broadcast_messages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::BroadcastMessages do +RSpec.describe API::BroadcastMessages, feature_category: :onboarding 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/bulk_imports_spec.rb b/spec/requests/api/bulk_imports_spec.rb index ad57a370fc5..13f079c69e7 100644 --- a/spec/requests/api/bulk_imports_spec.rb +++ b/spec/requests/api/bulk_imports_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::BulkImports do +RSpec.describe API::BulkImports, feature_category: :importers do let_it_be(:user) { create(:user) } let_it_be(:import_1) { create(:bulk_import, user: user) } let_it_be(:import_2) { create(:bulk_import, user: user) } @@ -50,6 +50,9 @@ RSpec.describe API::BulkImports do .to receive(:instance_version) .and_return( Gitlab::VersionInfo.new(::BulkImport::MIN_MAJOR_VERSION, ::BulkImport::MIN_MINOR_VERSION_FOR_PROJECT)) + allow(instance) + .to receive(:instance_enterprise) + .and_return(false) end end diff --git a/spec/requests/api/ci/job_artifacts_spec.rb b/spec/requests/api/ci/job_artifacts_spec.rb index da9eb6b2216..a4a38179d11 100644 --- a/spec/requests/api/ci/job_artifacts_spec.rb +++ b/spec/requests/api/ci/job_artifacts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::JobArtifacts do +RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do include HttpBasicAuthHelpers include DependencyProxyHelpers diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb index c1b7461f444..4e348ae64b6 100644 --- a/spec/requests/api/ci/jobs_spec.rb +++ b/spec/requests/api/ci/jobs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Jobs do +RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do include HttpBasicAuthHelpers include DependencyProxyHelpers @@ -190,15 +190,56 @@ RSpec.describe API::Ci::Jobs do describe 'GET /job/allowed_agents' do let_it_be(:group) { create(:group) } - let_it_be(:group_agent) { create(:cluster_agent, project: create(:project, group: group)) } - let_it_be(:group_authorization) { create(:agent_group_authorization, agent: group_agent, group: group) } - let_it_be(:project_agent) { create(:cluster_agent, project: project) } - before(:all) do - project.update!(group: group_authorization.group) + # create a different project for the group agents to reference + # otherwise the AgentAuthorizationsFinder will pick up the project.cluster_agents' implicit authorizations + let_it_be(:other_project) { create(:project, group: group) } + + let_it_be(:agent_authorizations_without_env) do + [ + create(:agent_group_authorization, agent: create(:cluster_agent, project: other_project), group: group), + create(:agent_project_authorization, agent: create(:cluster_agent, project: project), project: project), + Clusters::Agents::ImplicitAuthorization.new(agent: create(:cluster_agent, project: project)) + ] + end + + let_it_be(:agent_authorizations_with_review_and_production_env) do + [ + create( + :agent_group_authorization, + agent: create(:cluster_agent, project: other_project), + group: group, + environments: ['production', 'review/*'] + ), + create( + :agent_project_authorization, + agent: create(:cluster_agent, project: project), + project: project, + environments: ['production', 'review/*'] + ) + ] + end + + let_it_be(:agent_authorizations_with_staging_env) do + [ + create( + :agent_group_authorization, + agent: create(:cluster_agent, project: other_project), + group: group, + environments: ['staging'] + ), + create( + :agent_project_authorization, + agent: create(:cluster_agent, project: project), + project: project, + environments: ['staging'] + ) + ] end - let(:implicit_authorization) { Clusters::Agents::ImplicitAuthorization.new(agent: project_agent) } + before(:all) do + project.update!(group: group) + end let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token } } let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user, status: job_status) } @@ -215,30 +256,46 @@ RSpec.describe API::Ci::Jobs do context 'when token is valid and user is authorized' do shared_examples_for 'valid allowed_agents request' do - it 'returns agent info', :aggregate_failures do + it 'returns the job info', :aggregate_failures do expect(response).to have_gitlab_http_status(:ok) expect(json_response.dig('job', 'id')).to eq(job.id) expect(json_response.dig('pipeline', 'id')).to eq(job.pipeline_id) expect(json_response.dig('project', 'id')).to eq(job.project_id) - expect(json_response.dig('project', 'groups')).to match_array([{ 'id' => group_authorization.group.id }]) + expect(json_response.dig('project', 'groups')).to match_array([{ 'id' => group.id }]) expect(json_response.dig('user', 'id')).to eq(api_user.id) expect(json_response.dig('user', 'username')).to eq(api_user.username) expect(json_response.dig('user', 'roles_in_project')).to match_array %w(guest reporter developer) expect(json_response).not_to include('environment') - expect(json_response['allowed_agents']).to match_array( - [ - { - 'id' => implicit_authorization.agent_id, - 'config_project' => hash_including('id' => implicit_authorization.agent.project_id), - 'configuration' => implicit_authorization.config - }, - { - 'id' => group_authorization.agent_id, - 'config_project' => hash_including('id' => group_authorization.agent.project_id), - 'configuration' => group_authorization.config - } - ]) + end + + it 'returns the agents allowed for the job' do + expected_allowed_agents = agent_authorizations_without_env.map do |agent_auth| + { + 'id' => agent_auth.agent_id, + 'config_project' => hash_including('id' => agent_auth.agent.project_id), + 'configuration' => agent_auth.config + } + end + + expect(json_response['allowed_agents']).to match_array expected_allowed_agents + end + end + + shared_examples_for 'valid allowed_agents request for a job with environment' do + it 'return the agents configured for the given environment' do + expected_allowed_agents = ( + agent_authorizations_without_env + + agent_authorizations_with_review_and_production_env + ).map do |agent_auth| + { + 'id' => agent_auth.agent_id, + 'config_project' => hash_including('id' => agent_auth.agent.project_id), + 'configuration' => agent_auth.config + } + end + + expect(json_response['allowed_agents']).to match_array(expected_allowed_agents) end end @@ -254,21 +311,25 @@ RSpec.describe API::Ci::Jobs do it 'includes environment tier' do expect(json_response.dig('environment', 'tier')).to eq('production') end + + it_behaves_like 'valid allowed_agents request for a job with environment' end context 'when non-deployment environment action' do let(:job) do - create(:environment, name: 'review', project_id: project.id) - create(:ci_build, :artifacts, :stop_review_app, environment: 'review', pipeline: pipeline, user: api_user, status: job_status) + create(:environment, name: 'review/123', project_id: project.id) + create(:ci_build, :artifacts, :stop_review_app, environment: 'review/123', pipeline: pipeline, user: api_user, status: job_status) end it 'includes environment slug' do - expect(json_response.dig('environment', 'slug')).to eq('review') + expect(json_response.dig('environment', 'slug')).to match('review-123-.*') end it 'includes environment tier' do expect(json_response.dig('environment', 'tier')).to eq('development') end + + it_behaves_like 'valid allowed_agents request for a job with environment' end context 'when passing the token as params' do @@ -325,7 +386,7 @@ RSpec.describe API::Ci::Jobs do context 'authorized user' do it 'returns project jobs' do expect(response).to have_gitlab_http_status(:ok) - expect(response).to include_pagination_headers + expect(response).to include_limited_pagination_headers expect(json_response).to be_an Array end @@ -426,6 +487,46 @@ RSpec.describe API::Ci::Jobs do end end + describe 'GET /projects/:id/jobs rate limited' do + let(:query) { {} } + + context 'with the ci_enforce_rate_limits_jobs_api feature flag on' do + before do + stub_feature_flags(ci_enforce_rate_limits_jobs_api: true) + + allow_next_instance_of(Gitlab::ApplicationRateLimiter::BaseStrategy) do |strategy| + threshold = Gitlab::ApplicationRateLimiter.rate_limits[:jobs_index][:threshold] + allow(strategy).to receive(:increment).and_return(threshold + 1) + end + + get api("/projects/#{project.id}/jobs", api_user), params: query + end + + it 'enforces rate limits for the endpoint' do + expect(response).to have_gitlab_http_status :too_many_requests + expect(json_response['message']['error']).to eq('This endpoint has been requested too many times. Try again later.') + end + end + + context 'with the ci_enforce_rate_limits_jobs_api feature flag off' do + before do + stub_feature_flags(ci_enforce_rate_limits_jobs_api: false) + + allow_next_instance_of(Gitlab::ApplicationRateLimiter::BaseStrategy) do |strategy| + threshold = Gitlab::ApplicationRateLimiter.rate_limits[:jobs_index][:threshold] + allow(strategy).to receive(:increment).and_return(threshold + 1) + end + + get api("/projects/#{project.id}/jobs", api_user), params: query + end + + it 'makes a successful request' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_limited_pagination_headers + end + end + end + describe 'GET /projects/:id/jobs/:job_id' do before do |example| unless example.metadata[:skip_before_request] diff --git a/spec/requests/api/ci/pipeline_schedules_spec.rb b/spec/requests/api/ci/pipeline_schedules_spec.rb index 30badadde13..2a2c5f65aee 100644 --- a/spec/requests/api/ci/pipeline_schedules_spec.rb +++ b/spec/requests/api/ci/pipeline_schedules_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::PipelineSchedules do +RSpec.describe API::Ci::PipelineSchedules, feature_category: :continuous_integration do let_it_be(:developer) { create(:user) } let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :repository, public_builds: false) } diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb index c9d06f37c8b..6d69da85449 100644 --- a/spec/requests/api/ci/pipelines_spec.rb +++ b/spec/requests/api/ci/pipelines_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Pipelines do +RSpec.describe API::Ci::Pipelines, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:non_member) { create(:user) } let_it_be(:project2) { create(:project, creator: user) } diff --git a/spec/requests/api/ci/resource_groups_spec.rb b/spec/requests/api/ci/resource_groups_spec.rb index 2a67a3e4322..26265aec1dc 100644 --- a/spec/requests/api/ci/resource_groups_spec.rb +++ b/spec/requests/api/ci/resource_groups_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::ResourceGroups do +RSpec.describe API::Ci::ResourceGroups, feature_category: :continuous_delivery do let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb index 9af0541bd2c..1c119079c50 100644 --- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb +++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do +RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_category: :runner do include StubGitlabCalls include RedisHelpers include WorkhorseHelpers diff --git a/spec/requests/api/ci/runner/jobs_put_spec.rb b/spec/requests/api/ci/runner/jobs_put_spec.rb index 8c95748aa5f..22817922b1b 100644 --- a/spec/requests/api/ci/runner/jobs_put_spec.rb +++ b/spec/requests/api/ci/runner/jobs_put_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do +RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_category: :runner do include StubGitlabCalls include RedisHelpers include WorkhorseHelpers diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb index d69a3f5a980..d15bc9d2dd5 100644 --- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb +++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do +RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_category: :runner do include StubGitlabCalls include RedisHelpers include WorkhorseHelpers @@ -175,6 +175,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do 'allow_failure' => true }] end + let(:expected_hooks) do + [{ 'name' => 'pre_get_sources_script', 'script' => ["echo 'hello pre_get_sources_script'"] }] + end + let(:expected_variables) do [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false }, { 'key' => 'CI_JOB_STAGE', 'value' => 'test', 'public' => true, 'masked' => false }, @@ -230,6 +234,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do 'variables' => [{ 'key' => 'MYSQL_ROOT_PASSWORD', 'value' => 'root123.' }], 'pull_policy' => nil } ]) expect(json_response['steps']).to eq(expected_steps) + expect(json_response['hooks']).to eq(expected_hooks) expect(json_response['artifacts']).to eq(expected_artifacts) expect(json_response['cache']).to match(expected_cache) expect(json_response['variables']).to include(*expected_variables) @@ -769,6 +774,19 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do end end end + + context 'when the FF ci_hooks_pre_get_sources_script is disabled' do + before do + stub_feature_flags(ci_hooks_pre_get_sources_script: false) + end + + it 'does not return the pre_get_sources_script' do + request_job + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).not_to have_key('hooks') + end + end end describe 'port support' do diff --git a/spec/requests/api/ci/runner/jobs_trace_spec.rb b/spec/requests/api/ci/runner/jobs_trace_spec.rb index d42043a7fe5..de67cec0a27 100644 --- a/spec/requests/api/ci/runner/jobs_trace_spec.rb +++ b/spec/requests/api/ci/runner/jobs_trace_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks do +RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks, feature_category: :runner do include StubGitlabCalls include RedisHelpers include WorkhorseHelpers diff --git a/spec/requests/api/ci/runner/runners_delete_spec.rb b/spec/requests/api/ci/runner/runners_delete_spec.rb index 9d1bae7cce8..65c287a9535 100644 --- a/spec/requests/api/ci/runner/runners_delete_spec.rb +++ b/spec/requests/api/ci/runner/runners_delete_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do +RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_category: :runner_fleet do include StubGitlabCalls include RedisHelpers include WorkhorseHelpers diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb index 47302046865..73f8e87a9fb 100644 --- a/spec/requests/api/ci/runner/runners_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_post_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do +RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_category: :runner_fleet do describe '/api/v4/runners' do describe 'POST /api/v4/runners' do context 'when no token is provided' do diff --git a/spec/requests/api/ci/runner/runners_reset_spec.rb b/spec/requests/api/ci/runner/runners_reset_spec.rb index 02b66a89a0a..6ab21138d26 100644 --- a/spec/requests/api/ci/runner/runners_reset_spec.rb +++ b/spec/requests/api/ci/runner/runners_reset_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do +RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_category: :runner_fleet do include StubGitlabCalls include RedisHelpers include WorkhorseHelpers diff --git a/spec/requests/api/ci/runner/runners_verify_post_spec.rb b/spec/requests/api/ci/runner/runners_verify_post_spec.rb index 038e126deaa..22a954cc444 100644 --- a/spec/requests/api/ci/runner/runners_verify_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_verify_post_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do +RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_category: :runner do include StubGitlabCalls include RedisHelpers include WorkhorseHelpers diff --git a/spec/requests/api/ci/runners_reset_registration_token_spec.rb b/spec/requests/api/ci/runners_reset_registration_token_spec.rb index b8e4370fd46..1110dbf5fbc 100644 --- a/spec/requests/api/ci/runners_reset_registration_token_spec.rb +++ b/spec/requests/api/ci/runners_reset_registration_token_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runners do +RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do subject { post api("#{prefix}/runners/reset_registration_token", user) } shared_examples 'bad request' do |result| diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb index dd9894f2972..b07dd388390 100644 --- a/spec/requests/api/ci/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Runners do +RSpec.describe API::Ci::Runners, feature_category: :runner_fleet do let_it_be(:admin) { create(:user, :admin) } let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } diff --git a/spec/requests/api/ci/secure_files_spec.rb b/spec/requests/api/ci/secure_files_spec.rb index b0bca6e9125..700fd97152a 100644 --- a/spec/requests/api/ci/secure_files_spec.rb +++ b/spec/requests/api/ci/secure_files_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::SecureFiles do +RSpec.describe API::Ci::SecureFiles, feature_category: :pipeline_authoring do before do stub_ci_secure_file_object_storage stub_feature_flags(ci_secure_files: true) @@ -31,23 +31,6 @@ RSpec.describe API::Ci::SecureFiles do end describe 'GET /projects/:id/secure_files' do - context 'feature flag' do - it 'returns a 503 when the feature flag is disabled' do - stub_feature_flags(ci_secure_files: false) - - get api("/projects/#{project.id}/secure_files", maintainer) - - expect(response).to have_gitlab_http_status(:service_unavailable) - end - - it 'returns a 200 when the feature flag is enabled' do - get api("/projects/#{project.id}/secure_files", maintainer) - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_a(Array) - end - end - context 'ci_secure_files_read_only feature flag' do context 'when the flag is enabled' do before do diff --git a/spec/requests/api/ci/triggers_spec.rb b/spec/requests/api/ci/triggers_spec.rb index f9b7880a4c4..ff54ba61309 100644 --- a/spec/requests/api/ci/triggers_spec.rb +++ b/spec/requests/api/ci/triggers_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Triggers do +RSpec.describe API::Ci::Triggers, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } diff --git a/spec/requests/api/ci/variables_spec.rb b/spec/requests/api/ci/variables_spec.rb index cafb841995d..c5d01afb7c4 100644 --- a/spec/requests/api/ci/variables_spec.rb +++ b/spec/requests/api/ci/variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Ci::Variables do +RSpec.describe API::Ci::Variables, feature_category: :pipeline_authoring do let(:user) { create(:user) } let(:user2) { create(:user) } let!(:project) { create(:project, creator_id: user.id) } diff --git a/spec/requests/api/clusters/agent_tokens_spec.rb b/spec/requests/api/clusters/agent_tokens_spec.rb index a33bef53b14..b2d996e8002 100644 --- a/spec/requests/api/clusters/agent_tokens_spec.rb +++ b/spec/requests/api/clusters/agent_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Clusters::AgentTokens do +RSpec.describe API::Clusters::AgentTokens, feature_category: :kubernetes_management do let_it_be(:agent) { create(:cluster_agent) } let_it_be(:agent_token_one) { create(:cluster_agent_token, agent: agent) } let_it_be(:revoked_agent_token) { create(:cluster_agent_token, :revoked, agent: agent) } @@ -80,6 +80,27 @@ RSpec.describe API::Clusters::AgentTokens do end end + it 'returns an agent token that is revoked' do + get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{revoked_agent_token.id}", user) + + aggregate_failures "testing response" do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/agent_token') + expect(json_response['id']).to eq(revoked_agent_token.id) + expect(json_response['name']).to eq(revoked_agent_token.name) + expect(json_response['agent_id']).to eq(agent.id) + expect(json_response['status']).to eq('revoked') + end + end + + it 'returns a 404 if agent does not exist' do + path = "/projects/#{project.id}/cluster_agents/#{non_existing_record_id}/tokens/#{non_existing_record_id}" + + get api(path, user) + + expect(response).to have_gitlab_http_status(:not_found) + end + it 'returns a 404 error if agent token id is not available' do get api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{non_existing_record_id}", user) @@ -160,6 +181,21 @@ RSpec.describe API::Clusters::AgentTokens do expect(agent_token_one.reload).to be_revoked end + it 'returns a success response when revoking an already revoked agent token', :aggregate_failures do + delete api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{revoked_agent_token.id}", user) + + expect(response).to have_gitlab_http_status(:no_content) + expect(revoked_agent_token.reload).to be_revoked + end + + it 'returns a 404 error when given agent_id does not exist' do + path = "/projects/#{project.id}/cluster_agents/#{non_existing_record_id}/tokens/#{non_existing_record_id}" + + delete api(path, user) + + expect(response).to have_gitlab_http_status(:not_found) + end + it 'returns a 404 error when revoking non existent agent token' do delete api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens/#{non_existing_record_id}", user) diff --git a/spec/requests/api/clusters/agents_spec.rb b/spec/requests/api/clusters/agents_spec.rb index 5e3bdd69529..a09713bd6e7 100644 --- a/spec/requests/api/clusters/agents_spec.rb +++ b/spec/requests/api/clusters/agents_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Clusters::Agents do +RSpec.describe API::Clusters::Agents, feature_category: :kubernetes_management do let_it_be(:agent) { create(:cluster_agent) } let(:user) { agent.created_by_user } diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index dc5d9620dc4..025d065df7b 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::CommitStatuses do +RSpec.describe API::CommitStatuses, feature_category: :continuous_integration do let_it_be(:project) { create(:project, :repository) } let_it_be(:commit) { project.repository.commit } let_it_be(:guest) { create_user(:guest) } @@ -167,7 +167,7 @@ RSpec.describe API::CommitStatuses do let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: 'ref') } let(:params) { { state: 'pending' } } - shared_examples_for 'creates a commit status for the existing pipeline' do + shared_examples_for 'creates a commit status for the existing pipeline with an external stage' do it do expect do post api(post_url, developer), params: params @@ -176,19 +176,73 @@ RSpec.describe API::CommitStatuses do job = pipeline.statuses.find_by_name(json_response['name']) expect(response).to have_gitlab_http_status(:created) + expect(job.ci_stage.name).to eq('external') + expect(job.ci_stage.position).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) + expect(job.ci_stage.pipeline).to eq(pipeline) expect(job.status).to eq('pending') expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) end end - it_behaves_like 'creates a commit status for the existing pipeline' + shared_examples_for 'updates the commit status with an external stage' do + before do + post api(post_url, developer), params: { state: 'pending' } + end + + it 'updates the commit status with the external stage' do + post api(post_url, developer), params: { state: 'running' } + job = pipeline.statuses.find_by_name(json_response['name']) + + expect(job.ci_stage.name).to eq('external') + expect(job.ci_stage.position).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) + expect(job.ci_stage.pipeline).to eq(pipeline) + expect(job.status).to eq('running') + expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) + end + end context 'with pipeline for merge request' do let!(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project) } let!(:pipeline) { merge_request.all_pipelines.last } let(:sha) { pipeline.sha } - it_behaves_like 'creates a commit status for the existing pipeline' + it_behaves_like 'creates a commit status for the existing pipeline with an external stage' + end + + context 'when an external stage does not exist' do + context 'when the commit status does not exist' do + it_behaves_like 'creates a commit status for the existing pipeline with an external stage' + end + + context 'when the commit status exists' do + it_behaves_like 'updates the commit status with an external stage' + end + end + + context 'when an external stage already exists' do + let(:stage) { create(:ci_stage, name: 'external', pipeline: pipeline, position: 1_000_000) } + + context 'when the commit status exists' do + it_behaves_like 'updates the commit status with an external stage' + end + + context 'when the commit status does not exist' do + it_behaves_like 'creates a commit status for the existing pipeline with an external stage' + end + end + end + + context 'when the pipeline does not exist' do + it 'creates a commit status and a stage' do + expect do + post api(post_url, developer), params: { state: 'pending' } + end.to change { Ci::Pipeline.count }.by(1) + job = Ci::Pipeline.last.statuses.find_by_name(json_response['name']) + + expect(job.ci_stage.name).to eq('external') + expect(job.ci_stage.position).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) + expect(job.status).to eq('pending') + expect(job.stage_idx).to eq(GenericCommitStatus::EXTERNAL_STAGE_IDX) end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 8a08d5203fd..5874d764b00 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' -RSpec.describe API::Commits do +RSpec.describe API::Commits, feature_category: :source_code_management do include ProjectForksHelper include SessionHelpers @@ -492,12 +492,32 @@ RSpec.describe API::Commits do subject end - it_behaves_like 'Snowplow event tracking' do - let(:namespace) { project.namespace } - let(:category) { 'ide_edit' } - let(:action) { 'g_edit_by_web_ide' } + it_behaves_like 'Snowplow event tracking with RedisHLL context' do + let(:namespace) { project.namespace.reload } + let(:category) { 'Gitlab::UsageDataCounters::EditorUniqueCounter' } + let(:action) { 'ide_edit' } + let(:property) { 'g_edit_by_web_ide' } + let(:label) { 'usage_activity_by_stage_monthly.create.action_monthly_active_users_ide_edit' } + let(:context) { [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context] } let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } end + + context 'counts.web_ide_commits Snowplow event tracking' do + before do + allow(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_web_ide_edit_action) + end + + it_behaves_like 'Snowplow event tracking' do + let(:action) { :commit } + let(:category) { described_class.to_s } + let(:namespace) { project.namespace.reload } + let(:label) { 'counts.web_ide_commits' } + let(:feature_flag_name) { 'route_hll_to_snowplow_phase3' } + let(:context) do + [Gitlab::Tracking::ServicePingContext.new(data_source: :redis, key_path: 'counts.web_ide_commits').to_context.to_json] + end + end + end end context 'a new file in project repo' do @@ -2206,7 +2226,7 @@ RSpec.describe API::Commits do end describe 'GET /projects/:id/repository/commits/:sha/signature' do - let!(:project) { create(:project, :repository, :public) } + let_it_be(:project) { create(:project, :repository, :public) } let(:project_id) { project.id } let(:commit_id) { project.repository.commit.id } let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/signature" } @@ -2228,7 +2248,7 @@ RSpec.describe API::Commits do end context 'gpg signed commit' do - let(:commit) { project.repository.commit(GpgHelpers::SIGNED_COMMIT_SHA) } + let!(:commit) { project.commit(GpgHelpers::SIGNED_COMMIT_SHA) } let(:commit_id) { commit.id } it 'returns correct JSON' do @@ -2244,8 +2264,8 @@ RSpec.describe API::Commits do end context 'x509 signed commit' do - let(:commit) { project.repository.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') } - let(:commit_id) { commit.id } + let(:commit_id) { '189a6c924013fc3fe40d6f1ec1dc20214183bc97' } + let!(:commit) { project.commit(commit_id) } it 'returns correct JSON' do get api(route, current_user) @@ -2276,5 +2296,59 @@ RSpec.describe API::Commits do end end end + + context 'with ssh signed commit' do + let(:commit_id) { '7b5160f9bb23a3d58a0accdbe89da13b96b1ece9' } + let!(:commit) { project.commit(commit_id) } + + context 'when key belonging to author does not exist' do + it 'returns data without key' do + get api(route, current_user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['signature_type']).to eq('SSH') + expect(json_response['verification_status']).to eq(commit.signature.verification_status) + expect(json_response['key']).to be_nil + expect(json_response['commit_source']).to eq('gitaly') + end + end + + context 'when key belonging to author exists' do + let(:user) { create(:user, email: commit.committer_email) } + let!(:key) { create(:key, user: user, key: extract_public_key_from_commit(commit), expires_at: 2.days.from_now) } + + def extract_public_key_from_commit(commit) + ssh_commit = Gitlab::Ssh::Commit.new(commit) + signature_data = ::SSHData::Signature.parse_pem(ssh_commit.signature_text) + signature_data.public_key.openssh + end + + it 'returns data including key' do + get api(route, current_user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['signature_type']).to eq('SSH') + expect(json_response['verification_status']).to eq(commit.signature.verification_status) + expect(json_response['key']['id']).to eq(key.id) + expect(json_response['key']['title']).to eq(key.title) + expect(json_response['key']['key']).to eq(key.publishable_key) + expect(Time.parse(json_response['key']['created_at'])).to be_like_time(key.created_at) + expect(Time.parse(json_response['key']['expires_at'])).to be_like_time(key.expires_at) + expect(json_response['commit_source']).to eq('gitaly') + end + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(ssh_commit_signatures: false) + end + + it 'returns 404' do + get api(route, current_user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end end diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb index 53f3ef10743..0c726d46a01 100644 --- a/spec/requests/api/composer_packages_spec.rb +++ b/spec/requests/api/composer_packages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::ComposerPackages do +RSpec.describe API::ComposerPackages, feature_category: :package_registry do include HttpBasicAuthHelpers let_it_be(:user) { create(:user) } @@ -14,7 +14,10 @@ RSpec.describe API::ComposerPackages do let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) } let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } + let(:snowplow_gitlab_standard_context) do + { project: project, namespace: project.namespace, user: user, property: 'i_package_composer_user' } + end + let(:headers) { {} } using RSpec::Parameterized::TableSyntax @@ -491,7 +494,6 @@ RSpec.describe API::ComposerPackages do with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } before do project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) @@ -501,6 +503,10 @@ RSpec.describe API::ComposerPackages do include_context 'Composer user type', params[:user_role], params[:member] do if params[:expected_status] == :success + let(:snowplow_gitlab_standard_context) do + { project: project, namespace: project.namespace, property: 'i_package_composer_user' } + end + it_behaves_like 'a package tracking event', described_class.name, 'pull_package' else it_behaves_like 'not a package tracking event' @@ -509,6 +515,17 @@ RSpec.describe API::ComposerPackages do end it_behaves_like 'Composer publish with deploy tokens' + + context 'with access to package registry for everyone' do + let(:headers) { {} } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) + end + + it_behaves_like 'returning response status', :success + end end end diff --git a/spec/requests/api/conan_instance_packages_spec.rb b/spec/requests/api/conan_instance_packages_spec.rb index b343e0cfc97..0c1d94560b5 100644 --- a/spec/requests/api/conan_instance_packages_spec.rb +++ b/spec/requests/api/conan_instance_packages_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -RSpec.describe API::ConanInstancePackages do - let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } } +RSpec.describe API::ConanInstancePackages, feature_category: :package_registry do + let(:snowplow_gitlab_standard_context) { { user: user, project: project, namespace: project.namespace, property: 'i_package_conan_user' } } include_context 'conan api setup' diff --git a/spec/requests/api/conan_project_packages_spec.rb b/spec/requests/api/conan_project_packages_spec.rb index 4e6af9942ef..814745f9e29 100644 --- a/spec/requests/api/conan_project_packages_spec.rb +++ b/spec/requests/api/conan_project_packages_spec.rb @@ -1,11 +1,21 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::ConanProjectPackages do +RSpec.describe API::ConanProjectPackages, feature_category: :package_registry do include_context 'conan api setup' let(:project_id) { project.id } - let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } } + + shared_examples 'accept get request on private project with access to package registry for everyone' do + subject { get api(url) } + + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) + end + + it_behaves_like 'returning response status', :ok + end describe 'GET /api/v4/projects/:id/packages/conan/v1/ping' do let(:url) { "/projects/#{project.id}/packages/conan/v1/ping" } @@ -41,43 +51,50 @@ RSpec.describe API::ConanProjectPackages do include_context 'conan recipe endpoints' let(:url_prefix) { "#{Settings.gitlab.base_url}/api/v4/projects/#{project_id}" } + let(:recipe_path) { package.conan_recipe_path } + + subject { get api(url), headers: headers } describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do - let(:recipe_path) { package.conan_recipe_path } let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}" } it_behaves_like 'recipe snapshot endpoint' + it_behaves_like 'accept get request on private project with access to package registry for everyone' end describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference' do - let(:recipe_path) { package.conan_recipe_path } let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}" } it_behaves_like 'package snapshot endpoint' + it_behaves_like 'accept get request on private project with access to package registry for everyone' end describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/digest' do - subject { get api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/digest"), headers: headers } + let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/digest" } it_behaves_like 'recipe download_urls endpoint' + it_behaves_like 'accept get request on private project with access to package registry for everyone' end describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/download_urls' do - subject { get api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls"), headers: headers } + let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/download_urls" } it_behaves_like 'package download_urls endpoint' + it_behaves_like 'accept get request on private project with access to package registry for everyone' end describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/download_urls' do - subject { get api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/download_urls"), headers: headers } + let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/download_urls" } it_behaves_like 'recipe download_urls endpoint' + it_behaves_like 'accept get request on private project with access to package registry for everyone' end describe 'GET /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/digest' do - subject { get api("/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest"), headers: headers } + let(:url) { "/projects/#{project_id}/packages/conan/v1/conans/#{recipe_path}/packages/#{conan_package_reference}/digest" } it_behaves_like 'package download_urls endpoint' + it_behaves_like 'accept get request on private project with access to package registry for everyone' end describe 'POST /api/v4/projects/:id/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/upload_urls' do @@ -102,24 +119,22 @@ RSpec.describe API::ConanProjectPackages do context 'file download endpoints', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/326194' do include_context 'conan file download endpoints' + subject { get api(url), headers: headers } + describe 'GET /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/export/:file_name' do - subject do - get api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}"), - headers: headers - end + let(:url) { "/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/export/#{recipe_file.file_name}" } it_behaves_like 'recipe file download endpoint' it_behaves_like 'project not found by project id' + it_behaves_like 'accept get request on private project with access to package registry for everyone' end describe 'GET /api/v4/projects/:id/packages/conan/v1/files/:package_name/package_version/:package_username/:package_channel/:recipe_revision/package/:conan_package_reference/:package_revision/:file_name' do - subject do - get api("/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}"), - headers: headers - end + let(:url) { "/projects/#{project_id}/packages/conan/v1/files/#{recipe_path}/#{metadata.recipe_revision}/package/#{metadata.conan_package_reference}/#{metadata.package_revision}/#{package_file.file_name}" } it_behaves_like 'package file download endpoint' it_behaves_like 'project not found by project id' + it_behaves_like 'accept get request on private project with access to package registry for everyone' end end diff --git a/spec/requests/api/container_registry_event_spec.rb b/spec/requests/api/container_registry_event_spec.rb index 767e6e0b2ff..32c4b0e9598 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' -RSpec.describe API::ContainerRegistryEvent do +RSpec.describe API::ContainerRegistryEvent, feature_category: :container_registry 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/container_repositories_spec.rb b/spec/requests/api/container_repositories_spec.rb index 90f0243dbfc..4c1e52df4fc 100644 --- a/spec/requests/api/container_repositories_spec.rb +++ b/spec/requests/api/container_repositories_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ContainerRepositories do +RSpec.describe API::ContainerRepositories, feature_category: :container_registry do include_context 'container registry client stubs' let_it_be(:project) { create(:project, :private) } @@ -75,6 +75,13 @@ RSpec.describe API::ContainerRepositories do expect(json_response['id']).to eq(repository.id) expect(response.body).to include('tags') + expect(json_response['tags']).to eq(repository.tags.map do |tag| + { + "location" => tag.location, + "name" => tag.name, + "path" => tag.path + } + end) end context 'with a network error' do diff --git a/spec/requests/api/debian_group_packages_spec.rb b/spec/requests/api/debian_group_packages_spec.rb index 9dbb75becf8..f4d5ef3fe90 100644 --- a/spec/requests/api/debian_group_packages_spec.rb +++ b/spec/requests/api/debian_group_packages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::DebianGroupPackages do +RSpec.describe API::DebianGroupPackages, feature_category: :package_registry do include HttpBasicAuthHelpers include WorkhorseHelpers diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb index 6bef669cb3a..c27e165b39b 100644 --- a/spec/requests/api/debian_project_packages_spec.rb +++ b/spec/requests/api/debian_project_packages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::DebianProjectPackages do +RSpec.describe API::DebianProjectPackages, feature_category: :package_registry do include HttpBasicAuthHelpers include WorkhorseHelpers diff --git a/spec/requests/api/dependency_proxy_spec.rb b/spec/requests/api/dependency_proxy_spec.rb index 7af4ed08cb8..ef94cdbbe2b 100644 --- a/spec/requests/api/dependency_proxy_spec.rb +++ b/spec/requests/api/dependency_proxy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::DependencyProxy, api: true do +RSpec.describe API::DependencyProxy, api: true, feature_category: :dependency_proxy do let_it_be(:user) { create(:user) } let_it_be(:blob) { create(:dependency_proxy_blob) } let_it_be(:group, reload: true) { blob.group } diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 1daa7c38e04..15880d920c5 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::DeployKeys do +RSpec.describe API::DeployKeys, feature_category: :continuous_delivery do let_it_be(:user) { create(:user) } let_it_be(:maintainer) { create(:user) } let_it_be(:admin) { create(:admin) } diff --git a/spec/requests/api/deploy_tokens_spec.rb b/spec/requests/api/deploy_tokens_spec.rb index e0296248a03..4efe49e843f 100644 --- a/spec/requests/api/deploy_tokens_spec.rb +++ b/spec/requests/api/deploy_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::DeployTokens do +RSpec.describe API::DeployTokens, feature_category: :continuous_delivery do let_it_be(:user) { create(:user) } let_it_be(:creator) { create(:user) } let_it_be(:project) { create(:project, creator_id: creator.id) } @@ -346,7 +346,7 @@ RSpec.describe API::DeployTokens do context 'deploy token creation' do shared_examples 'creating a deploy token' do |entity, unauthenticated_response, authorized_role| - let(:expires_time) { 1.year.from_now } + let(:expires_time) { 1.year.from_now.to_datetime } let(:params) do { name: 'Foo', @@ -414,6 +414,14 @@ RSpec.describe API::DeployTokens do it { is_expected.to have_gitlab_http_status(:bad_request) } end + + context 'with an invalid expires_at date' do + before do + params[:expires_at] = 'foo' + end + + it { is_expected.to have_gitlab_http_status(:bad_request) } + end end end diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index 8124080abea..efe76c9cfda 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Deployments do +RSpec.describe API::Deployments, feature_category: :continuous_delivery do let_it_be(:user) { create(:user) } let_it_be(:non_member) { create(:user) } @@ -16,8 +16,8 @@ RSpec.describe API::Deployments do let_it_be(:staging) { create(:environment, :staging, project: project) } let_it_be(:build) { create(:ci_build, :success, project: project) } let_it_be(:deployment_1) { create(:deployment, :success, project: project, environment: production, deployable: build, ref: 'master', created_at: Time.now, updated_at: Time.now) } - let_it_be(:deployment_2) { create(:deployment, :success, project: project, environment: staging, deployable: build, ref: 'master', created_at: 1.day.ago, updated_at: 2.hours.ago) } - let_it_be(:deployment_3) { create(:deployment, :success, project: project, environment: staging, deployable: build, ref: 'master', created_at: 2.days.ago, updated_at: 1.hour.ago) } + let_it_be(:deployment_2) { create(:deployment, :success, project: project, environment: staging, deployable: build, ref: 'master', created_at: 1.day.ago, finished_at: 2.hours.ago, updated_at: 2.hours.ago) } + let_it_be(:deployment_3) { create(:deployment, :success, project: project, environment: staging, deployable: build, ref: 'master', created_at: 2.days.ago, finished_at: 1.hour.ago, updated_at: 1.hour.ago) } def perform_request(params = {}) get api("/projects/#{project.id}/deployments", user), params: params @@ -47,7 +47,7 @@ RSpec.describe API::Deployments do end context 'when forbidden order_by is specified' do - it 'returns projects deployments with last update in specified datetime range' do + it 'returns an error' do perform_request({ updated_before: 30.minutes.ago, updated_after: 90.minutes.ago, order_by: :id }) expect(response).to have_gitlab_http_status(:bad_request) @@ -56,6 +56,44 @@ RSpec.describe API::Deployments do end end + context 'with finished after and before filters specified' do + context 'for successful deployments' do + it 'returns projects deployments finished before the specified datetime range' do + perform_request({ status: :success, finished_before: 90.minutes.ago, order_by: :finished_at, environment: 'staging' }) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.first['id']).to eq(deployment_2.id) + end + + it 'returns projects deployments finished after the specified datetime range' do + perform_request({ status: :success, finished_after: 90.minutes.ago, order_by: :finished_at, environment: 'staging' }) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.first['id']).to eq(deployment_3.id) + end + end + + context 'for unsuccessful deployments' do + it 'returns an error' do + perform_request({ status: :failed, finished_before: 30.minutes.ago, order_by: :finished_at }) + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to include('`finished_at` filter must be combined with `success` status filter.') + end + end + + context 'when a forbidden order_by is specified' do + it 'returns an error' do + perform_request({ status: :success, finished_before: 30.minutes.ago, order_by: :id }) + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to include('`finished_at` filter requires `finished_at` sort.') + end + end + end + context 'with the environment filter specifed' do it 'returns deployments for the environment' do perform_request({ environment: production.name }) diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb index 258bd26c05a..38016375b8f 100644 --- a/spec/requests/api/discussions_spec.rb +++ b/spec/requests/api/discussions_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Discussions do +RSpec.describe API::Discussions, feature_category: :team_planning do let(:user) { create(:user) } let!(:project) { create(:project, :public, :repository, namespace: user.namespace) } let(:private_user) { create(:user) } @@ -29,6 +29,73 @@ RSpec.describe API::Discussions do end end + context 'when noteable is a WorkItem' do + let!(:work_item) { create(:work_item, :issue, project: project, author: user) } + let!(:work_item_note) { create(:discussion_note_on_issue, noteable: work_item, project: project, author: user) } + + let(:parent) { project } + let(:noteable) { work_item } + let(:note) { work_item_note } + let(:url) { "/projects/#{parent.id}/issues/#{noteable[:iid]}/discussions" } + + it_behaves_like 'discussions API', 'projects', 'issues', 'iid', can_reply_to_individual_notes: true + + context 'with work item without notes widget' do + before do + stub_const('WorkItems::Type::BASE_TYPES', { issue: { name: 'NoNotesWidget', enum_value: 0 } }) + stub_const('WorkItems::Type::WIDGETS_FOR_TYPE', { issue: [::WorkItems::Widgets::Description] }) + end + + context 'when fetching discussions' do + it "returns 404" do + get api(url, user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when single fetching discussion by discussion_id' do + it "returns 404" do + get api("#{url}/#{work_item_note.discussion_id}", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when trying to create a new discussion' do + it "returns 404" do + post api(url, user), params: { body: 'hi!' } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when trying to create a new comment on a discussion' do + it 'returns 404' do + post api("#{url}/#{note.discussion_id}/notes", user), params: { body: 'Hello!' } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when trying to update a new comment on a discussion' do + it 'returns 404' do + put api("#{url}/notes/#{note.id}", user), params: { body: 'Update Hello!' } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when deleting a note' do + it 'returns 404' do + delete api("#{url}/#{note.discussion_id}/notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end + context 'when noteable is a Snippet' do let!(:snippet) { create(:project_snippet, project: project, author: user) } let!(:snippet_note) { create(:discussion_note_on_project_snippet, noteable: snippet, project: project, author: user) } diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb index 14da9a600cd..5116f074894 100644 --- a/spec/requests/api/doorkeeper_access_spec.rb +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'doorkeeper access' do +RSpec.describe 'doorkeeper access', feature_category: :authentication_and_authorization 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 a35c1630caa..d06e70a1a02 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Environments do +RSpec.describe API::Environments, feature_category: :continuous_delivery do let_it_be(:user) { create(:user) } let_it_be(:non_member) { create(:user) } let_it_be(:project) { create(:project, :private, :repository, namespace: user.namespace) } @@ -321,8 +321,8 @@ RSpec.describe API::Environments do expect(json_response["scheduled_entries"].size).to eq(1) expect(json_response["scheduled_entries"].first["id"]).to eq(old_stopped_review_env.id) expect(json_response["unprocessable_entries"].size).to eq(0) - expect(json_response["scheduled_entries"]).to match_schema('public_api/v4/environments') - expect(json_response["unprocessable_entries"]).to match_schema('public_api/v4/environments') + expect(json_response["scheduled_entries"]).to match_schema('public_api/v4/basic_environments') + expect(json_response["unprocessable_entries"]).to match_schema('public_api/v4/basic_environments') expect(old_stopped_review_env.reload.auto_delete_at).to eq(1.week.from_now) expect(new_stopped_review_env.reload.auto_delete_at).to be_nil diff --git a/spec/requests/api/error_tracking/client_keys_spec.rb b/spec/requests/api/error_tracking/client_keys_spec.rb index ba4d713dff2..cb840e1cffa 100644 --- a/spec/requests/api/error_tracking/client_keys_spec.rb +++ b/spec/requests/api/error_tracking/client_keys_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ErrorTracking::ClientKeys do +RSpec.describe API::ErrorTracking::ClientKeys, feature_category: :error_tracking do let_it_be(:guest) { create(:user) } let_it_be(:maintainer) { create(:user) } let_it_be(:setting) { create(:project_error_tracking_setting) } diff --git a/spec/requests/api/error_tracking/collector_spec.rb b/spec/requests/api/error_tracking/collector_spec.rb index dfca994d1c3..6a3e71bc859 100644 --- a/spec/requests/api/error_tracking/collector_spec.rb +++ b/spec/requests/api/error_tracking/collector_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ErrorTracking::Collector do +RSpec.describe API::ErrorTracking::Collector, feature_category: :error_tracking do let_it_be(:project) { create(:project, :private) } let_it_be(:setting) { create(:project_error_tracking_setting, :integrated, project: project) } let_it_be(:client_key) { create(:error_tracking_client_key, project: project) } diff --git a/spec/requests/api/error_tracking/project_settings_spec.rb b/spec/requests/api/error_tracking/project_settings_spec.rb index c0c0680ef31..5906cdf105a 100644 --- a/spec/requests/api/error_tracking/project_settings_spec.rb +++ b/spec/requests/api/error_tracking/project_settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ErrorTracking::ProjectSettings do +RSpec.describe API::ErrorTracking::ProjectSettings, feature_category: :error_tracking do let_it_be(:user) { create(:user) } let(:setting) { create(:project_error_tracking_setting) } diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb index d6c3999f22f..5c061a37ff3 100644 --- a/spec/requests/api/events_spec.rb +++ b/spec/requests/api/events_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Events do +RSpec.describe API::Events, feature_category: :users 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/feature_flags_spec.rb b/spec/requests/api/feature_flags_spec.rb index bf7eec167f5..69e3633de57 100644 --- a/spec/requests/api/feature_flags_spec.rb +++ b/spec/requests/api/feature_flags_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::FeatureFlags do +RSpec.describe API::FeatureFlags, feature_category: :feature_flags do include FeatureFlagHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/feature_flags_user_lists_spec.rb b/spec/requests/api/feature_flags_user_lists_spec.rb index bfc57042ff4..443cbbea147 100644 --- a/spec/requests/api/feature_flags_user_lists_spec.rb +++ b/spec/requests/api/feature_flags_user_lists_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::FeatureFlagsUserLists do +RSpec.describe API::FeatureFlagsUserLists, feature_category: :feature_flags do let_it_be(:project, refind: true) { create(:project) } let_it_be(:client, refind: true) { create(:operations_feature_flags_client, project: project) } let_it_be(:developer) { create(:user) } diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb index 85dafef569d..9f1af746080 100644 --- a/spec/requests/api/features_spec.rb +++ b/spec/requests/api/features_spec.rb @@ -2,8 +2,9 @@ require 'spec_helper' -RSpec.describe API::Features, stub_feature_flags: false do - let_it_be(:user) { create(:user) } +RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feature_flags do + let_it_be(:user) { create(:user) } + let_it_be(:opted_out) { create(:user) } let_it_be(:admin) { create(:admin) } # Find any `development` feature flag name @@ -35,7 +36,10 @@ RSpec.describe API::Features, stub_feature_flags: false do { 'name' => 'feature_1', 'state' => 'on', - 'gates' => [{ 'key' => 'boolean', 'value' => true }], + 'gates' => [ + { 'key' => 'boolean', 'value' => true }, + { 'key' => 'actors', 'value' => ["#{opted_out.flipper_id}:opt_out"] } + ], 'definition' => nil }, { @@ -64,6 +68,7 @@ RSpec.describe API::Features, stub_feature_flags: false do before do Feature.enable('feature_1') + Feature.opt_out('feature_1', opted_out) Feature.disable('feature_2') Feature.enable('feature_3', Feature.group(:perf_team)) Feature.enable(known_feature_flag.name) @@ -654,12 +659,53 @@ RSpec.describe API::Features, stub_feature_flags: false do it_behaves_like 'sets the feature flag status' + it 'opts given actors out' do + Feature.enable(feature_name) + expect(Feature.enabled?(feature_name, user)).to be_truthy + + post api("/features/#{feature_name}", admin), params: { value: 'opt_out', user: user.username } + + expect(response).to have_gitlab_http_status(:created) + expect(json_response).to include( + 'name' => feature_name, + 'state' => 'on', + 'gates' => [ + { 'key' => 'boolean', 'value' => true }, + { 'key' => 'actors', 'value' => ["#{user.flipper_id}:opt_out"] } + ] + ) + end + + context 'when the actor has opted-out' do + before do + Feature.enable(feature_name) + Feature.opt_out(feature_name, user) + end + + it 'refuses to enable the feature' do + post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username } + + expect(Feature).not_to be_enabled(feature_name, user) + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + context 'when feature flag set_feature_flag_service is disabled' do before do stub_feature_flags(set_feature_flag_service: false) end it_behaves_like 'sets the feature flag status' + + it 'rejects opt_out requests' do + Feature.enable(feature_name) + expect(Feature).to be_enabled(feature_name, user) + + post api("/features/#{feature_name}", admin), params: { value: 'opt_out', user: user.username } + + expect(response).to have_gitlab_http_status(:bad_request) + end end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index d4d3aace204..9cee3c06bb1 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Files do +RSpec.describe API::Files, feature_category: :source_code_management do include RepoHelpers let_it_be(:group) { create(:group, :public) } @@ -11,8 +11,12 @@ RSpec.describe API::Files do let_it_be(:inherited_reporter) { create(:user) } let_it_be(:inherited_developer) { create(:user) } - let!(:project) { create(:project, :repository, namespace: user.namespace) } - let(:guest) { create(:user) { |u| project.add_guest(u) } } + let_it_be_with_reload(:project) { create(:project, :repository, namespace: user.namespace) } + let_it_be_with_reload(:public_project) { create(:project, :public, :repository) } + let_it_be_with_reload(:private_project) { create(:project, :private, :repository, group: group) } + let_it_be_with_reload(:public_project_private_repo) { create(:project, :public, :repository, :repository_private, group: group) } + + let_it_be(:guest) { create(:user) { |u| project.add_guest(u) } } let(:file_path) { 'files%2Fruby%2Fpopen%2Erb' } let(:file_name) { 'popen.rb' } let(:last_commit_id) { '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' } @@ -183,8 +187,9 @@ RSpec.describe API::Files do context 'when unauthenticated' do context 'and project is public' do + let(:project) { public_project } + it_behaves_like 'repository files' do - let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -361,7 +366,7 @@ RSpec.describe API::Files do context 'when unauthenticated' do context 'and project is public' do it_behaves_like 'repository files' do - let(:project) { create(:project, :public, :repository) } + let(:project) { public_project } let(:current_user) { nil } let(:api_user) { nil } end @@ -406,7 +411,7 @@ RSpec.describe API::Files do context 'when authenticated' do context 'and user is an inherited member from the group' do context 'when project is public with private repository' do - let_it_be(:project) { create(:project, :public, :repository, :repository_private, group: group) } + let(:project) { public_project_private_repo } context 'and user is a guest' do it_behaves_like 'returns non-executable file attributes as json' do @@ -428,7 +433,7 @@ RSpec.describe API::Files do end context 'when project is private' do - let_it_be(:project) { create(:project, :private, :repository, group: group) } + let(:project) { private_project } context 'and user is a guest' do it_behaves_like '403 response' do @@ -655,7 +660,7 @@ RSpec.describe API::Files do context 'when unauthenticated' do context 'and project is public' do it_behaves_like 'repository blame files' do - let(:project) { create(:project, :public, :repository) } + let(:project) { public_project } let(:current_user) { nil } end end @@ -774,12 +779,69 @@ RSpec.describe API::Files do let(:request) { get api(route(file_path), current_user), params: params } end end + + context 'when lfs parameter is true and the project has lfs enabled' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + project.update_attribute(:lfs_enabled, true) + end + + let(:request) { get api(route(file_path) + '/raw', current_user), params: params.merge(lfs: true) } + let(:file_path) { 'files%2Flfs%2Flfs_object.iso' } + + it_behaves_like '404 response' + + context 'and the file has an lfs object' do + let_it_be(:lfs_object) { create(:lfs_object, :with_file, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897') } + + it_behaves_like '404 response' + + context 'and the project has access to the lfs object' do + before do + project.lfs_objects << lfs_object + end + + context 'and lfs uses local file storage' do + before do + Grape::Endpoint.before_each do |endpoint| + allow(endpoint).to receive(:sendfile).with(lfs_object.file.path) + end + end + + after do + Grape::Endpoint.before_each nil + end + + it 'responds with the lfs object file' do + request + expect(response.headers["Content-Disposition"]).to eq( + "attachment; filename=\"#{lfs_object.file.filename}\"; filename*=UTF-8''#{lfs_object.file.filename}" + ) + end + end + + context 'and lfs uses remote object storage' do + before do + stub_lfs_object_storage + lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE) + end + + it 'redirects to the lfs object file' do + request + + expect(response).to have_gitlab_http_status(:found) + expect(response.location).to include(lfs_object.reload.file.path) + end + end + end + end + end end context 'when unauthenticated' do context 'and project is public' do it_behaves_like 'repository raw files' do - let(:project) { create(:project, :public, :repository) } + let(:project) { public_project } let(:current_user) { nil } end end @@ -821,7 +883,7 @@ RSpec.describe API::Files do end describe 'POST /projects/:id/repository/files/:file_path' do - let!(:file_path) { 'new_subfolder%2Fnewfile%2Erb' } + let(:file_path) { FFaker::Guid.guid } let(:params) do { @@ -939,14 +1001,13 @@ RSpec.describe API::Files do it_behaves_like 'creates a new file in the project repo' do let(:current_user) { user } - let(:file_path) { 'newfile%2Erb' } + let(:file_path) { FFaker::Guid.guid } end end context 'when specifying an author' do it 'creates a new file with the specified author' do params.merge!(author_email: author_email, author_name: author_name) - post api(route('new_file_with_author%2Etxt'), user), params: params expect(response).to have_gitlab_http_status(:created) @@ -963,7 +1024,7 @@ RSpec.describe API::Files do context 'when authenticated' do context 'and user is an inherited member from the group' do context 'when project is public with private repository' do - let_it_be(:project) { create(:project, :public, :repository, :repository_private, group: group) } + let(:project) { public_project_private_repo } context 'and user is a guest' do it_behaves_like '403 response' do @@ -985,7 +1046,7 @@ RSpec.describe API::Files do end context 'when project is private' do - let_it_be(:project) { create(:project, :private, :repository, group: group) } + let(:project) { private_project } context 'and user is a guest' do it_behaves_like '403 response' do @@ -1161,64 +1222,76 @@ RSpec.describe API::Files do } end - it 'returns 400 when file path is invalid' do - delete api(route(invalid_file_path), user), params: params + describe 'when files are deleted' do + let(:file_path) { FFaker::Guid.guid } - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['error']).to eq(invalid_file_message) - end + before do + create_file_in_repo(project, 'master', 'master', file_path, 'Test file') + end - it_behaves_like 'when path is absolute' do - subject { delete api(route(absolute_path), user), params: params } - end + it 'deletes existing file in project repo' do + delete api(route(file_path), user), params: params - it 'deletes existing file in project repo' do - delete api(route(file_path), user), params: params + expect(response).to have_gitlab_http_status(:no_content) + end - expect(response).to have_gitlab_http_status(:no_content) - end + context 'when specifying an author' do + before do + params.merge!(author_email: author_email, author_name: author_name) + end - context 'when no params given' do - it 'returns a 400 bad request' do - delete api(route(file_path), user) + it 'removes a file with the specified author' do + delete api(route(file_path), user), params: params - expect(response).to have_gitlab_http_status(:bad_request) + expect(response).to have_gitlab_http_status(:no_content) + end end end - context 'when the commit message is empty' do - before do - params[:commit_message] = '' + describe 'when files are not deleted' do + it_behaves_like 'when path is absolute' do + subject { delete api(route(absolute_path), user), params: params } end - it 'returns a 400 bad request' do - delete api(route(file_path), user), params: params + it 'returns 400 when file path is invalid' do + delete api(route(invalid_file_path), user), params: params expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq(invalid_file_message) end - end - context 'when fails to delete file' do - before do - allow_next_instance_of(Repository) do |instance| - allow(instance).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file') + context 'when no params given' do + it 'returns a 400 bad request' do + delete api(route(file_path), user) + + expect(response).to have_gitlab_http_status(:bad_request) end end - it 'returns a 400 bad request' do - delete api(route(file_path), user), params: params + context 'when the commit message is empty' do + before do + params[:commit_message] = '' + end - expect(response).to have_gitlab_http_status(:bad_request) + it 'returns a 400 bad request' do + delete api(route(file_path), user), params: params + + expect(response).to have_gitlab_http_status(:bad_request) + end end - end - context 'when specifying an author' do - it 'removes a file with the specified author' do - params.merge!(author_email: author_email, author_name: author_name) + context 'when fails to delete file' do + before do + allow_next_instance_of(Repository) do |instance| + allow(instance).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file') + end + end - delete api(route(file_path), user), params: params + it 'returns a 400 bad request' do + delete api(route(file_path), user), params: params - expect(response).to have_gitlab_http_status(:no_content) + expect(response).to have_gitlab_http_status(:bad_request) + end end end end diff --git a/spec/requests/api/freeze_periods_spec.rb b/spec/requests/api/freeze_periods_spec.rb index 3da992301d5..170871706dc 100644 --- a/spec/requests/api/freeze_periods_spec.rb +++ b/spec/requests/api/freeze_periods_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::FreezePeriods do +RSpec.describe API::FreezePeriods, feature_category: :continuous_delivery 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/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb index 0478e123086..6b3f378a4bc 100644 --- a/spec/requests/api/generic_packages_spec.rb +++ b/spec/requests/api/generic_packages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GenericPackages do +RSpec.describe API::GenericPackages, feature_category: :package_registry do include HttpBasicAuthHelpers using RSpec::Parameterized::TableSyntax @@ -19,7 +19,7 @@ RSpec.describe API::GenericPackages do let(:user) { personal_access_token.user } let(:ci_build) { create(:ci_build, :running, user: user, project: project) } - let(:snowplow_standard_context_params) { { user: user, project: project, namespace: project.namespace } } + let(:snowplow_gitlab_standard_context) { { user: user, project: project, namespace: project.namespace, property: 'i_package_generic_user' } } def auth_header return {} if user_role == :anonymous @@ -408,8 +408,6 @@ RSpec.describe API::GenericPackages do end context 'event tracking' do - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } - subject { upload_file(params, workhorse_headers.merge(personal_access_token_header)) } it_behaves_like 'a package tracking event', described_class.name, 'push_package' @@ -645,8 +643,6 @@ RSpec.describe API::GenericPackages do end context 'event tracking' do - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } - before do project.add_developer(user) end diff --git a/spec/requests/api/geo_spec.rb b/spec/requests/api/geo_spec.rb index 4e77fa9405c..3dec91fd2fa 100644 --- a/spec/requests/api/geo_spec.rb +++ b/spec/requests/api/geo_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Geo do +RSpec.describe API::Geo, feature_category: :geo_replication do include WorkhorseHelpers describe 'GET /geo/proxy' do diff --git a/spec/requests/api/go_proxy_spec.rb b/spec/requests/api/go_proxy_spec.rb index 5498ed6df13..17189087ade 100644 --- a/spec/requests/api/go_proxy_spec.rb +++ b/spec/requests/api/go_proxy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GoProxy do +RSpec.describe API::GoProxy, feature_category: :package_registry do include PackagesManagerApiSpecHelpers include HttpBasicAuthHelpers diff --git a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb index 9bed720c815..2775c3d4c5a 100644 --- a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb +++ b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'get board lists' do +RSpec.describe 'get board lists', feature_category: :team_planning do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/boards/board_list_query_spec.rb b/spec/requests/api/graphql/boards/board_list_query_spec.rb index f01f7e87f10..b5ed0fe35d5 100644 --- a/spec/requests/api/graphql/boards/board_list_query_spec.rb +++ b/spec/requests/api/graphql/boards/board_list_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Querying a Board list' do +RSpec.describe 'Querying a Board list', feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } 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 ad7df5c9344..2f23e93e2c6 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' -RSpec.describe 'get board lists' do +RSpec.describe 'get board lists', feature_category: :team_planning 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 50004e5a8a1..1407034fa5f 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' -RSpec.describe 'get list of boards' do +RSpec.describe 'get list of boards', feature_category: :team_planning do include GraphqlHelpers include_context 'group and project boards query context' diff --git a/spec/requests/api/graphql/ci/application_setting_spec.rb b/spec/requests/api/graphql/ci/application_setting_spec.rb index 156ee550f16..42ab1786fee 100644 --- a/spec/requests/api/graphql/ci/application_setting_spec.rb +++ b/spec/requests/api/graphql/ci/application_setting_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting Application Settings' do +RSpec.describe 'getting Application Settings', feature_category: :continuous_integration do include GraphqlHelpers let(:fields) do diff --git a/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb index 2dc7b9764fe..0437a30eccd 100644 --- a/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb +++ b/spec/requests/api/graphql/ci/ci_cd_setting_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'Getting Ci Cd Setting' do +RSpec.describe 'Getting Ci Cd Setting', feature_category: :continuous_integration do include GraphqlHelpers let_it_be_with_reload(:project) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/ci/config_spec.rb b/spec/requests/api/graphql/ci/config_spec.rb index 784019ee926..8154f132430 100644 --- a/spec/requests/api/graphql/ci/config_spec.rb +++ b/spec/requests/api/graphql/ci/config_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.ciConfig' do +RSpec.describe 'Query.ciConfig', feature_category: :continuous_integration do include GraphqlHelpers include StubRequests diff --git a/spec/requests/api/graphql/ci/config_variables_spec.rb b/spec/requests/api/graphql/ci/config_variables_spec.rb index 17133d7ea66..e6d73701b8f 100644 --- a/spec/requests/api/graphql/ci/config_variables_spec.rb +++ b/spec/requests/api/graphql/ci/config_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).ciConfigVariables(sha)' do +RSpec.describe 'Query.project(fullPath).ciConfigVariables(sha)', feature_category: :pipeline_authoring do include GraphqlHelpers include ReactiveCachingHelpers diff --git a/spec/requests/api/graphql/ci/group_variables_spec.rb b/spec/requests/api/graphql/ci/group_variables_spec.rb index 7baf26c7648..51cbb4719f7 100644 --- a/spec/requests/api/graphql/ci/group_variables_spec.rb +++ b/spec/requests/api/graphql/ci/group_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.group(fullPath).ciVariables' do +RSpec.describe 'Query.group(fullPath).ciVariables', feature_category: :pipeline_authoring do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/ci/groups_spec.rb b/spec/requests/api/graphql/ci/groups_spec.rb index d1a4395d2c9..d1588833d8f 100644 --- a/spec/requests/api/graphql/ci/groups_spec.rb +++ b/spec/requests/api/graphql/ci/groups_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'Query.project.pipeline.stages.groups' do +RSpec.describe 'Query.project.pipeline.stages.groups', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project, :repository, :public) } diff --git a/spec/requests/api/graphql/ci/instance_variables_spec.rb b/spec/requests/api/graphql/ci/instance_variables_spec.rb index cd6b2de98a1..e0397e17923 100644 --- a/spec/requests/api/graphql/ci/instance_variables_spec.rb +++ b/spec/requests/api/graphql/ci/instance_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.ciVariables' do +RSpec.describe 'Query.ciVariables', feature_category: :pipeline_authoring do include GraphqlHelpers let(:query) do diff --git a/spec/requests/api/graphql/ci/job_artifacts_spec.rb b/spec/requests/api/graphql/ci/job_artifacts_spec.rb index df6e398fbe5..5fcb363d479 100644 --- a/spec/requests/api/graphql/ci/job_artifacts_spec.rb +++ b/spec/requests/api/graphql/ci/job_artifacts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).pipelines.jobs.artifacts' do +RSpec.describe 'Query.project(fullPath).pipelines.jobs.artifacts', feature_category: :build do include GraphqlHelpers let_it_be(:project) { create(:project, :repository, :public) } diff --git a/spec/requests/api/graphql/ci/job_spec.rb b/spec/requests/api/graphql/ci/job_spec.rb index 3721155c71b..8121c5e5c85 100644 --- a/spec/requests/api/graphql/ci/job_spec.rb +++ b/spec/requests/api/graphql/ci/job_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).pipelines.job(id)' do +RSpec.describe 'Query.project(fullPath).pipelines.job(id)', feature_category: :continuous_integration do include GraphqlHelpers around do |example| diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb index a161c5c98ed..7a1dc614dcf 100644 --- a/spec/requests/api/graphql/ci/jobs_spec.rb +++ b/spec/requests/api/graphql/ci/jobs_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'Query.project.pipeline' do +RSpec.describe 'Query.project.pipeline', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project, :repository, :public) } @@ -367,4 +367,65 @@ RSpec.describe 'Query.project.pipeline' do expect_graphql_errors_to_include [/"jobs" field can be requested only for 1 Project\(s\) at a time./] end end + + context 'when batched querying jobs for multiple projects' do + let(:batched) do + [ + { query: query_1 }, + { query: query_2 } + ] + end + + let(:query_1) do + %( + query Page1 { + projects { + nodes { + jobs { + nodes { + name + } + } + } + } + } + ) + end + + let(:query_2) do + %( + query Page2 { + projects { + nodes { + jobs { + nodes { + name + } + } + } + } + } + ) + end + + before do + create_list(:project, 2).each do |project| + project.add_developer(user) + create(:ci_build, project: project) + end + end + + it 'limits the specific field evaluation per query' do + get_multiplex(batched, current_user: user) + + resp = json_response + + expect(resp.first.dig('data', 'projects', 'nodes').first.dig('jobs', 'nodes').first['name']).to eq('test') + expect(resp.first['errors'].first['message']) + .to match(/"jobs" field can be requested only for 1 Project\(s\) at a time./) + expect(resp.second.dig('data', 'projects', 'nodes').first.dig('jobs', 'nodes').first['name']).to eq('test') + expect(resp.second['errors'].first['message']) + .to match(/"jobs" field can be requested only for 1 Project\(s\) at a time./) + end + end end diff --git a/spec/requests/api/graphql/ci/manual_variables_spec.rb b/spec/requests/api/graphql/ci/manual_variables_spec.rb index a15bac2b8bd..921c69e535d 100644 --- a/spec/requests/api/graphql/ci/manual_variables_spec.rb +++ b/spec/requests/api/graphql/ci/manual_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).pipelines.jobs.manualVariables' do +RSpec.describe 'Query.project(fullPath).pipelines.jobs.manualVariables', feature_category: :pipeline_authoring do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/ci/pipeline_schedules_spec.rb b/spec/requests/api/graphql/ci/pipeline_schedules_spec.rb index 8b8ba09a95c..76adce6ff1b 100644 --- a/spec/requests/api/graphql/ci/pipeline_schedules_spec.rb +++ b/spec/requests/api/graphql/ci/pipeline_schedules_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' -RSpec.describe 'Query.project.pipelineSchedules' do +RSpec.describe 'Query.project.pipelineSchedules', feature_category: :continuous_integration do include GraphqlHelpers - let_it_be(:project) { create(:project, :repository, :public) } let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository, :public, creator: user, namespace: user.namespace) } let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) } let(:pipeline_schedule_graphql_data) { graphql_data_at(:project, :pipeline_schedules, :nodes, 0) } @@ -29,6 +29,8 @@ RSpec.describe 'Query.project.pipelineSchedules' do forTag cron cronTimezone + editPath + variables { nodes { #{all_graphql_fields_for('PipelineScheduleVariable')} } } } QUERY end @@ -61,6 +63,58 @@ RSpec.describe 'Query.project.pipelineSchedules' do expect(pipeline_schedule_graphql_data['refPath']).to eq("/#{project.full_path}/-/commits/#{ref_for_display}") expect(pipeline_schedule_graphql_data['forTag']).to be(false) end + + it 'returns the edit_path for a pipeline schedule' do + edit_path = pipeline_schedule_graphql_data['editPath'] + + expect(edit_path).to eq("/#{project.full_path}/-/pipeline_schedules/#{pipeline_schedule.id}/edit") + end + end + + describe 'variables' do + let!(:env_vars) { create_list(:ci_pipeline_schedule_variable, 5, pipeline_schedule: pipeline_schedule) } + + it 'returns all variables' do + post_graphql(query, current_user: user) + + variables = pipeline_schedule_graphql_data['variables']['nodes'] + expected = env_vars.map do |var| + a_graphql_entity_for(var, :key, :value, variable_type: var.variable_type.upcase) + end + + expect(variables).to match_array(expected) + end + + it 'is N+1 safe on the variables level' do + baseline = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: user) } + + create_list(:ci_pipeline_schedule_variable, 2, pipeline_schedule: pipeline_schedule) + + expect { post_graphql(query, current_user: user) }.not_to exceed_query_limit(baseline) + end + + it 'is N+1 safe on the schedules level' do + baseline = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: user) } + + pipeline_schedule_2 = create(:ci_pipeline_schedule, project: project, owner: user) + create_list(:ci_pipeline_schedule_variable, 2, pipeline_schedule: pipeline_schedule_2) + + expect { post_graphql(query, current_user: user) }.not_to exceed_query_limit(baseline) + end + end + + describe 'permissions' do + let_it_be(:another_user) { create(:user) } + + before do + post_graphql(query, current_user: another_user) + end + + it 'does not return the edit_path for a pipeline schedule for a user that does not have permissions' do + edit_path = pipeline_schedule_graphql_data['editPath'] + + expect(edit_path).to be nil + end end it 'avoids N+1 queries' do diff --git a/spec/requests/api/graphql/ci/pipelines_spec.rb b/spec/requests/api/graphql/ci/pipelines_spec.rb index 948704e8770..9fe71533b5e 100644 --- a/spec/requests/api/graphql/ci/pipelines_spec.rb +++ b/spec/requests/api/graphql/ci/pipelines_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).pipelines' do +RSpec.describe 'Query.project(fullPath).pipelines', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project, :repository, :public) } diff --git a/spec/requests/api/graphql/ci/project_variables_spec.rb b/spec/requests/api/graphql/ci/project_variables_spec.rb index d49a4a7e768..0338b58a0ea 100644 --- a/spec/requests/api/graphql/ci/project_variables_spec.rb +++ b/spec/requests/api/graphql/ci/project_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).ciVariables' do +RSpec.describe 'Query.project(fullPath).ciVariables', feature_category: :pipeline_authoring do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb index 94c0a3c41bd..ca08e780758 100644 --- a/spec/requests/api/graphql/ci/runner_spec.rb +++ b/spec/requests/api/graphql/ci/runner_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.runner(id)' do +RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do include GraphqlHelpers let_it_be(:user) { create(:user, :admin) } @@ -74,34 +74,39 @@ RSpec.describe 'Query.runner(id)' do runner_data = graphql_data_at(:runner) expect(runner_data).not_to be_nil - expect(runner_data).to match a_hash_including( - 'id' => runner.to_global_id.to_s, - 'description' => runner.description, - 'createdAt' => runner.created_at&.iso8601, - 'contactedAt' => runner.contacted_at&.iso8601, - 'version' => runner.version, - 'shortSha' => runner.short_sha, - 'revision' => runner.revision, - 'locked' => false, - 'active' => runner.active, - 'paused' => !runner.active, - 'status' => runner.status('14.5').to_s.upcase, - 'maximumTimeout' => runner.maximum_timeout, - 'accessLevel' => runner.access_level.to_s.upcase, - 'runUntagged' => runner.run_untagged, - 'ipAddress' => runner.ip_address, - 'runnerType' => runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE', - 'executorName' => runner.executor_type&.dasherize, - 'architectureName' => runner.architecture, - 'platformName' => runner.platform, - 'maintenanceNote' => runner.maintenance_note, - 'maintenanceNoteHtml' => + expect(runner_data).to match a_graphql_entity_for( + runner, + description: runner.description, + created_at: runner.created_at&.iso8601, + contacted_at: runner.contacted_at&.iso8601, + version: runner.version, + short_sha: runner.short_sha, + revision: runner.revision, + locked: false, + active: runner.active, + paused: !runner.active, + status: runner.status('14.5').to_s.upcase, + job_execution_status: runner.builds.running.any? ? 'RUNNING' : 'IDLE', + maximum_timeout: runner.maximum_timeout, + access_level: runner.access_level.to_s.upcase, + run_untagged: runner.run_untagged, + ip_address: runner.ip_address, + runner_type: runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE', + executor_name: runner.executor_type&.dasherize, + architecture_name: runner.architecture, + platform_name: runner.platform, + maintenance_note: runner.maintenance_note, + maintenance_note_html: runner.maintainer_note.present? ? a_string_including('<strong>Test maintenance note</strong>') : '', - 'jobCount' => 0, - 'jobs' => a_hash_including("count" => 0, "nodes" => [], "pageInfo" => anything), - 'projectCount' => nil, - 'adminUrl' => "http://localhost/admin/runners/#{runner.id}", - 'userPermissions' => { + job_count: runner.builds.count, + jobs: a_hash_including( + "count" => runner.builds.count, + "nodes" => an_instance_of(Array), + "pageInfo" => anything + ), + project_count: nil, + admin_url: "http://localhost/admin/runners/#{runner.id}", + user_permissions: { 'readRunner' => true, 'updateRunner' => true, 'deleteRunner' => true, @@ -129,10 +134,7 @@ RSpec.describe 'Query.runner(id)' do runner_data = graphql_data_at(:runner) expect(runner_data).not_to be_nil - expect(runner_data).to match a_hash_including( - 'id' => runner.to_global_id.to_s, - 'adminUrl' => nil - ) + expect(runner_data).to match a_graphql_entity_for(runner, admin_url: nil) expect(runner_data['tagList']).to match_array runner.tag_list end end @@ -179,6 +181,16 @@ RSpec.describe 'Query.runner(id)' do expect(runner_data).not_to include('tagList') end end + + context 'with build running' do + before do + project = create(:project, :repository) + pipeline = create(:ci_pipeline, project: project) + create(:ci_build, :running, runner: runner, pipeline: pipeline) + end + + it_behaves_like 'runner details fetch' + end end describe 'for project runner' do @@ -216,9 +228,47 @@ RSpec.describe 'Query.runner(id)' do runner_data = graphql_data_at(:runner) - expect(runner_data).to match a_hash_including( - 'id' => project_runner.to_global_id.to_s, - 'locked' => is_locked + expect(runner_data).to match a_graphql_entity_for(project_runner, locked: is_locked) + end + end + end + + describe 'jobCount' do + let_it_be(:pipeline1) { create(:ci_pipeline, project: project1) } + let_it_be(:pipeline2) { create(:ci_pipeline, project: project1) } + let_it_be(:build1) { create(:ci_build, :running, runner: active_project_runner, pipeline: pipeline1) } + let_it_be(:build2) { create(:ci_build, :running, runner: active_project_runner, pipeline: pipeline2) } + + let(:runner_query_fragment) { 'id jobCount' } + let(:query) do + %( + query { + runner1: runner(id: "#{active_project_runner.to_global_id}") { #{runner_query_fragment} } + runner2: runner(id: "#{inactive_instance_runner.to_global_id}") { #{runner_query_fragment} } + } + ) + end + + it 'retrieves correct jobCount values' do + post_graphql(query, current_user: user) + + expect(graphql_data).to match a_hash_including( + 'runner1' => a_graphql_entity_for(active_project_runner, job_count: 2), + 'runner2' => a_graphql_entity_for(inactive_instance_runner, job_count: 0) + ) + end + + context 'when JOB_COUNT_LIMIT is in effect' do + before do + stub_const('Types::Ci::RunnerType::JOB_COUNT_LIMIT', 0) + end + + it 'retrieves correct capped jobCount values' do + post_graphql(query, current_user: user) + + expect(graphql_data).to match a_hash_including( + 'runner1' => a_graphql_entity_for(active_project_runner, job_count: 1), + 'runner2' => a_graphql_entity_for(inactive_instance_runner, job_count: 0) ) end end @@ -243,18 +293,8 @@ RSpec.describe 'Query.runner(id)' do post_graphql(query, current_user: user) expect(graphql_data).to match a_hash_including( - 'runner1' => { - 'id' => runner1.to_global_id.to_s, - 'ownerProject' => { - 'id' => project2.to_global_id.to_s - } - }, - 'runner2' => { - 'id' => runner2.to_global_id.to_s, - 'ownerProject' => { - 'id' => project1.to_global_id.to_s - } - } + 'runner1' => a_graphql_entity_for(runner1, owner_project: a_graphql_entity_for(project2)), + 'runner2' => a_graphql_entity_for(runner2, owner_project: a_graphql_entity_for(project1)) ) end end @@ -284,8 +324,8 @@ RSpec.describe 'Query.runner(id)' do it 'retrieves groups field with expected value' do post_graphql(query, current_user: user) - runner_data = graphql_data_at(:runner, :groups) - expect(runner_data).to eq 'nodes' => [{ 'id' => group.to_global_id.to_s }] + runner_data = graphql_data_at(:runner, :groups, :nodes) + expect(runner_data).to contain_exactly(a_graphql_entity_for(group)) end end @@ -409,13 +449,13 @@ RSpec.describe 'Query.runner(id)' do 'jobCount' => 1, 'jobs' => a_hash_including( "count" => 1, - "nodes" => [{ "id" => job.to_global_id.to_s, "status" => job.status.upcase }] + "nodes" => [a_graphql_entity_for(job, status: job.status.upcase)] ), 'projectCount' => 2, 'projects' => { 'nodes' => [ - { 'id' => project1.to_global_id.to_s }, - { 'id' => project2.to_global_id.to_s } + a_graphql_entity_for(project1), + a_graphql_entity_for(project2) ] }) expect(runner2_data).to match a_hash_including( @@ -486,15 +526,24 @@ RSpec.describe 'Query.runner(id)' do groups { nodes { id + path + fullPath + webUrl } } projects { nodes { id + path + fullPath + webUrl } } ownerProject { id + path + fullPath + webUrl } } SINGLE @@ -503,8 +552,8 @@ RSpec.describe 'Query.runner(id)' do let(:active_project_runner2) { create(:ci_runner, :project) } let(:active_group_runner2) { create(:ci_runner, :group) } - # Currently excluding known N+1 issues, see https://gitlab.com/gitlab-org/gitlab/-/issues/334759 - let(:excluded_fields) { %w[jobCount groups projects ownerProject] } + # Exclude fields that are already hardcoded above + let(:excluded_fields) { %w[jobs groups projects ownerProject] } let(:single_query) do <<~QUERY @@ -542,27 +591,98 @@ RSpec.describe 'Query.runner(id)' do expect(graphql_data.count).to eq 6 expect(graphql_data).to match( a_hash_including( - 'instance_runner1' => a_hash_including('id' => active_instance_runner.to_global_id.to_s), - 'instance_runner2' => a_hash_including('id' => inactive_instance_runner.to_global_id.to_s), - 'group_runner1' => a_hash_including( - 'id' => active_group_runner.to_global_id.to_s, - 'groups' => { 'nodes' => [a_hash_including('id' => group.to_global_id.to_s)] } + 'instance_runner1' => a_graphql_entity_for(active_instance_runner), + 'instance_runner2' => a_graphql_entity_for(inactive_instance_runner), + 'group_runner1' => a_graphql_entity_for( + active_group_runner, + groups: { 'nodes' => contain_exactly(a_graphql_entity_for(group)) } ), - 'group_runner2' => a_hash_including( - 'id' => active_group_runner2.to_global_id.to_s, - 'groups' => { 'nodes' => [a_hash_including('id' => active_group_runner2.groups[0].to_global_id.to_s)] } + 'group_runner2' => a_graphql_entity_for( + active_group_runner2, + groups: { 'nodes' => active_group_runner2.groups.map { |g| a_graphql_entity_for(g) } } ), - 'project_runner1' => a_hash_including( - 'id' => active_project_runner.to_global_id.to_s, - 'projects' => { 'nodes' => [a_hash_including('id' => active_project_runner.projects[0].to_global_id.to_s)] }, - 'ownerProject' => a_hash_including('id' => active_project_runner.projects[0].to_global_id.to_s) + 'project_runner1' => a_graphql_entity_for( + active_project_runner, + projects: { 'nodes' => active_project_runner.projects.map { |p| a_graphql_entity_for(p) } }, + owner_project: a_graphql_entity_for(active_project_runner.projects[0]) ), - 'project_runner2' => a_hash_including( - 'id' => active_project_runner2.to_global_id.to_s, - 'projects' => { - 'nodes' => [a_hash_including('id' => active_project_runner2.projects[0].to_global_id.to_s)] - }, - 'ownerProject' => a_hash_including('id' => active_project_runner2.projects[0].to_global_id.to_s) + 'project_runner2' => a_graphql_entity_for( + active_project_runner2, + projects: { 'nodes' => active_project_runner2.projects.map { |p| a_graphql_entity_for(p) } }, + owner_project: a_graphql_entity_for(active_project_runner2.projects[0]) + ) + )) + end + end + + describe 'Query limits with jobs' do + let!(:group1) { create(:group) } + let!(:group2) { create(:group) } + let!(:project1) { create(:project, :repository, group: group1) } + let!(:project2) { create(:project, :repository, group: group1) } + let!(:project3) { create(:project, :repository, group: group2) } + + let!(:merge_request1) { create(:merge_request, source_project: project1) } + let!(:merge_request2) { create(:merge_request, source_project: project3) } + + let(:project_runner2) { create(:ci_runner, :project, projects: [project1, project2]) } + let!(:build1) { create(:ci_build, :success, name: 'Build One', runner: project_runner2, pipeline: pipeline1) } + let!(:pipeline1) do + create(:ci_pipeline, project: project1, source: :merge_request_event, merge_request: merge_request1, ref: 'main', + target_sha: 'xxx') + end + + let(:query) do + <<~QUERY + { + runner(id: "#{project_runner2.to_global_id}") { + id + jobs { + nodes { + id + detailedStatus { + id + detailsPath + group + icon + text + } + shortSha + commitPath + finishedAt + duration + queuedDuration + tags + } + } + } + } + QUERY + end + + it 'does not execute more queries per job', :aggregate_failures do + # warm-up license cache and so on: + personal_access_token = create(:personal_access_token, user: user) + args = { current_user: user, token: { personal_access_token: personal_access_token } } + post_graphql(query, **args) + + control = ActiveRecord::QueryRecorder.new(query_recorder_debug: true) { post_graphql(query, **args) } + + # Add a new build to project_runner2 + project_runner2.runner_projects << build(:ci_runner_project, runner: project_runner2, project: project3) + pipeline2 = create(:ci_pipeline, project: project3, source: :merge_request_event, merge_request: merge_request2, + ref: 'main', target_sha: 'xxx') + build2 = create(:ci_build, :success, name: 'Build Two', runner: project_runner2, pipeline: pipeline2) + + args[:current_user] = create(:user, :admin) # do not reuse same user + expect { post_graphql(query, **args) }.not_to exceed_all_query_limit(control) + + expect(graphql_data.count).to eq 1 + expect(graphql_data).to match( + a_hash_including( + 'runner' => a_graphql_entity_for( + project_runner2, + jobs: { 'nodes' => containing_exactly(a_graphql_entity_for(build1), a_graphql_entity_for(build2)) } ) )) end diff --git a/spec/requests/api/graphql/ci/runner_web_url_edge_spec.rb b/spec/requests/api/graphql/ci/runner_web_url_edge_spec.rb index 767e958ea82..e84a1ca4cc4 100644 --- a/spec/requests/api/graphql/ci/runner_web_url_edge_spec.rb +++ b/spec/requests/api/graphql/ci/runner_web_url_edge_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'RunnerWebUrlEdge' do +RSpec.describe 'RunnerWebUrlEdge', feature_category: :runner_fleet do include GraphqlHelpers describe 'inside a Query.group' do diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb index 3054b866812..75d8609dc38 100644 --- a/spec/requests/api/graphql/ci/runners_spec.rb +++ b/spec/requests/api/graphql/ci/runners_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'Query.runners' do +RSpec.describe 'Query.runners', feature_category: :runner_fleet do include GraphqlHelpers let_it_be(:current_user) { create_default(:user, :admin) } @@ -48,7 +48,7 @@ RSpec.describe 'Query.runners' do it_behaves_like 'a working graphql query' it 'returns expected runner' do - expect(runners_graphql_data['nodes'].map { |n| n['id'] }).to contain_exactly(expected_runner.to_global_id.to_s) + expect(runners_graphql_data['nodes']).to contain_exactly(a_graphql_entity_for(expected_runner)) end end diff --git a/spec/requests/api/graphql/ci/stages_spec.rb b/spec/requests/api/graphql/ci/stages_spec.rb index 1edd6e58486..f4e1a69d455 100644 --- a/spec/requests/api/graphql/ci/stages_spec.rb +++ b/spec/requests/api/graphql/ci/stages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'Query.project.pipeline.stages' do +RSpec.describe 'Query.project.pipeline.stages', feature_category: :continuous_integration do include GraphqlHelpers subject(:post_query) { post_graphql(query, current_user: user) } diff --git a/spec/requests/api/graphql/ci/template_spec.rb b/spec/requests/api/graphql/ci/template_spec.rb index 1bbef7d7f30..aaec219f734 100644 --- a/spec/requests/api/graphql/ci/template_spec.rb +++ b/spec/requests/api/graphql/ci/template_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'Querying CI template' do +RSpec.describe 'Querying CI template', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project, :public) } diff --git a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb index 14c55e61a65..88f63fd59d7 100644 --- a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb +++ b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'container repository details' do +RSpec.describe 'container repository details', feature_category: :container_registry do include_context 'container registry tags' include_context 'container registry client stubs' diff --git a/spec/requests/api/graphql/crm/contacts_spec.rb b/spec/requests/api/graphql/crm/contacts_spec.rb index a676e92dc3b..3ae19de63ed 100644 --- a/spec/requests/api/graphql/crm/contacts_spec.rb +++ b/spec/requests/api/graphql/crm/contacts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting CRM contacts' do +RSpec.describe 'getting CRM contacts', feature_category: :service_desk do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/current_user/groups_query_spec.rb b/spec/requests/api/graphql/current_user/groups_query_spec.rb index 6e36beb2afc..151d07ff0a7 100644 --- a/spec/requests/api/graphql/current_user/groups_query_spec.rb +++ b/spec/requests/api/graphql/current_user/groups_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query current user groups' do +RSpec.describe 'Query current user groups', feature_category: :subgroups do include GraphqlHelpers let_it_be(:user) { create(:user) } 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 5a45f0db518..f7e23aeb241 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' -RSpec.describe 'Query current user todos' do +RSpec.describe 'Query current user todos', feature_category: :source_code_management do include GraphqlHelpers include DesignManagementTestHelpers @@ -19,7 +19,7 @@ RSpec.describe 'Query current user todos' do let(:fields) do <<~QUERY nodes { - #{all_graphql_fields_for('todos'.classify)} + #{all_graphql_fields_for('todos'.classify, max_depth: 2)} } QUERY end diff --git a/spec/requests/api/graphql/current_user_query_spec.rb b/spec/requests/api/graphql/current_user_query_spec.rb index 086a57094ca..53d2580caee 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' -RSpec.describe 'getting project information' do +RSpec.describe 'getting project information', feature_category: :authentication_and_authorization do include GraphqlHelpers let(:fields) do diff --git a/spec/requests/api/graphql/current_user_todos_spec.rb b/spec/requests/api/graphql/current_user_todos_spec.rb index da1c893ec2b..eaed51982e1 100644 --- a/spec/requests/api/graphql/current_user_todos_spec.rb +++ b/spec/requests/api/graphql/current_user_todos_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe 'A Todoable that implements the CurrentUserTodos interface' do +RSpec.describe 'A Todoable that implements the CurrentUserTodos interface', +feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/custom_emoji_query_spec.rb b/spec/requests/api/graphql/custom_emoji_query_spec.rb index 5dd5ad117b0..7b804623e01 100644 --- a/spec/requests/api/graphql/custom_emoji_query_spec.rb +++ b/spec/requests/api/graphql/custom_emoji_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting custom emoji within namespace' do +RSpec.describe 'getting custom emoji within namespace', feature_category: :not_owned do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/environments/deployments_query_spec.rb b/spec/requests/api/graphql/environments/deployments_spec.rb index 6da00057449..0022a38d2d3 100644 --- a/spec/requests/api/graphql/environments/deployments_query_spec.rb +++ b/spec/requests/api/graphql/environments/deployments_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Environments Deployments query' do +RSpec.describe 'Environments Deployments query', feature_category: :continuous_delivery do include GraphqlHelpers let_it_be(:project) { create(:project, :private, :repository) } @@ -437,6 +437,43 @@ RSpec.describe 'Environments Deployments query' do end end + context 'when requesting user permissions' do + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + environment(name: "#{environment.name}") { + deployments { + nodes { + iid + userPermissions { + updateDeployment + destroyDeployment + } + } + } + } + } + } + ) + end + + it_behaves_like 'avoids N+1 database queries' + + it 'returns user permissions of the deployments', :aggregate_failures do + deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes') + + deployments.each do |deployment| + deployment_in_record = project.deployments.find_by_iid(deployment['iid']) + + expect(deployment['userPermissions']['updateDeployment']) + .to eq(Ability.allowed?(user, :update_deployment, deployment_in_record)) + expect(deployment['userPermissions']['destroyDeployment']) + .to eq(Ability.allowed?(user, :destroy_deployment, deployment_in_record)) + end + end + end + describe 'sorting and pagination' do let(:data_path) { [:project, :environment, :deployments] } let(:current_user) { user } diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index c1beadb6c45..7937091ea7c 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' -RSpec.describe 'GitlabSchema configurations' do +RSpec.describe 'GitlabSchema configurations', feature_category: :not_owned do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/group/container_repositories_spec.rb b/spec/requests/api/graphql/group/container_repositories_spec.rb index 8ec321c8d7c..51d12261247 100644 --- a/spec/requests/api/graphql/group/container_repositories_spec.rb +++ b/spec/requests/api/graphql/group/container_repositories_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting container repositories in a group' do +RSpec.describe 'getting container repositories in a group', feature_category: :source_code_management do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb index daa1483e956..2c4770a31a7 100644 --- a/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb +++ b/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting dependency proxy blobs in a group' do +RSpec.describe 'getting dependency proxy blobs in a group', feature_category: :dependency_proxy do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb index cc706c3051f..aca8527ba0a 100644 --- a/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb +++ b/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting dependency proxy settings for a group' do +RSpec.describe 'getting dependency proxy settings for a group', feature_category: :dependency_proxy do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb index 3b2b04b1322..edff4dc1dae 100644 --- a/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb +++ b/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting dependency proxy image ttl policy for a group' do +RSpec.describe 'getting dependency proxy image ttl policy for a group', feature_category: :dependency_proxy do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb index 37ef7089c2f..d2d686104ad 100644 --- a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb +++ b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting dependency proxy manifests in a group' do +RSpec.describe 'getting dependency proxy manifests in a group', feature_category: :dependency_proxy do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/group/group_members_spec.rb b/spec/requests/api/graphql/group/group_members_spec.rb index 5f8becc0726..26d1fb48408 100644 --- a/spec/requests/api/graphql/group/group_members_spec.rb +++ b/spec/requests/api/graphql/group/group_members_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting group members information' do +RSpec.describe 'getting group members information', feature_category: :subgroups do include GraphqlHelpers let_it_be(:parent_group) { create(:group, :public) } diff --git a/spec/requests/api/graphql/group/issues_spec.rb b/spec/requests/api/graphql/group/issues_spec.rb index 26338f46611..95aeed32558 100644 --- a/spec/requests/api/graphql/group/issues_spec.rb +++ b/spec/requests/api/graphql/group/issues_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting an issue list for a group' do +RSpec.describe 'getting an issue list for a group', feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/group/labels_query_spec.rb b/spec/requests/api/graphql/group/labels_query_spec.rb index 31556ffca30..28886f8d80b 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' -RSpec.describe 'getting group label information' do +RSpec.describe 'getting group label information', feature_category: :team_planning do include GraphqlHelpers let_it_be(:group) { create(:group, :public) } diff --git a/spec/requests/api/graphql/group/merge_requests_spec.rb b/spec/requests/api/graphql/group/merge_requests_spec.rb index 434b0d16569..6976685ecc0 100644 --- a/spec/requests/api/graphql/group/merge_requests_spec.rb +++ b/spec/requests/api/graphql/group/merge_requests_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' # Based on ee/spec/requests/api/epics_spec.rb # Should follow closely in order to ensure all situations are covered -RSpec.describe 'Query.group.mergeRequests' do +RSpec.describe 'Query.group.mergeRequests', feature_category: :code_review do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/group/milestones_spec.rb b/spec/requests/api/graphql/group/milestones_spec.rb index 7c51409f907..28cd68493c0 100644 --- a/spec/requests/api/graphql/group/milestones_spec.rb +++ b/spec/requests/api/graphql/group/milestones_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Milestones through GroupQuery' do +RSpec.describe 'Milestones through GroupQuery', feature_category: :team_planning do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/group/packages_spec.rb b/spec/requests/api/graphql/group/packages_spec.rb index cf8736db5af..0b4057c87f8 100644 --- a/spec/requests/api/graphql/group/packages_spec.rb +++ b/spec/requests/api/graphql/group/packages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting a package list for a group' do +RSpec.describe 'getting a package list for a group', feature_category: :package_registry do include GraphqlHelpers let_it_be(:resource) { create(:group, :private) } diff --git a/spec/requests/api/graphql/group/recent_issue_boards_query_spec.rb b/spec/requests/api/graphql/group/recent_issue_boards_query_spec.rb index 4914beec870..2dfbc95bac9 100644 --- a/spec/requests/api/graphql/group/recent_issue_boards_query_spec.rb +++ b/spec/requests/api/graphql/group/recent_issue_boards_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting group recent issue boards' do +RSpec.describe 'getting group recent issue boards', feature_category: :team_planning do include GraphqlHelpers it_behaves_like 'querying a GraphQL type recent boards' do diff --git a/spec/requests/api/graphql/group/timelogs_spec.rb b/spec/requests/api/graphql/group/timelogs_spec.rb index 05b6ee3ff89..b67b39edff2 100644 --- a/spec/requests/api/graphql/group/timelogs_spec.rb +++ b/spec/requests/api/graphql/group/timelogs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Timelogs through GroupQuery' do +RSpec.describe 'Timelogs through GroupQuery', feature_category: :team_planning do include GraphqlHelpers describe 'Get list of timelogs from a group issues' do diff --git a/spec/requests/api/graphql/group/work_item_types_spec.rb b/spec/requests/api/graphql/group/work_item_types_spec.rb index 35090e2a89f..791c0fb9524 100644 --- a/spec/requests/api/graphql/group/work_item_types_spec.rb +++ b/spec/requests/api/graphql/group/work_item_types_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting a list of work item types for a group' do +RSpec.describe 'getting a list of work item types for a group', feature_category: :team_planning do include GraphqlHelpers let_it_be(:developer) { create(:user) } diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb index 8ee5c3c5d73..bc288c0a98b 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 -RSpec.describe 'getting group information' do +RSpec.describe 'getting group information', feature_category: :subgroups do include GraphqlHelpers include UploadHelpers diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb index 6e2d736f244..101de692aa5 100644 --- a/spec/requests/api/graphql/issue/issue_spec.rb +++ b/spec/requests/api/graphql/issue/issue_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.issue(id)' do +RSpec.describe 'Query.issue(id)', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/issue_status_counts_spec.rb b/spec/requests/api/graphql/issue_status_counts_spec.rb index 89ecbf44b10..72a1968cb27 100644 --- a/spec/requests/api/graphql/issue_status_counts_spec.rb +++ b/spec/requests/api/graphql/issue_status_counts_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting Issue counts by status' do +RSpec.describe 'getting Issue counts by status', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/issues_spec.rb b/spec/requests/api/graphql/issues_spec.rb index 8838ad78f72..ba6f8ec2cab 100644 --- a/spec/requests/api/graphql/issues_spec.rb +++ b/spec/requests/api/graphql/issues_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting an issue list at root level' do +RSpec.describe 'getting an issue list at root level', feature_category: :team_planning do include GraphqlHelpers let_it_be(:developer) { create(:user) } @@ -13,34 +13,81 @@ RSpec.describe 'getting an issue list at root level' do let_it_be(:project_b) { create(:project, :repository, :private, group: group1) } let_it_be(:project_c) { create(:project, :repository, :public, group: group2) } let_it_be(:project_d) { create(:project, :repository, :private, group: group2) } - let_it_be(:early_milestone) { create(:milestone, project: project_d, due_date: 10.days.from_now) } - let_it_be(:late_milestone) { create(:milestone, project: project_c, due_date: 30.days.from_now) } + let_it_be(:milestone1) { create(:milestone, project: project_c, due_date: 10.days.from_now) } + let_it_be(:milestone2) { create(:milestone, project: project_d, due_date: 20.days.from_now) } + let_it_be(:milestone3) { create(:milestone, project: project_d, due_date: 30.days.from_now) } + let_it_be(:milestone4) { create(:milestone, project: project_a, due_date: 40.days.from_now) } let_it_be(:priority1) { create(:label, project: project_c, priority: 1) } let_it_be(:priority2) { create(:label, project: project_d, priority: 5) } let_it_be(:priority3) { create(:label, project: project_a, priority: 10) } + let_it_be(:priority4) { create(:label, project: project_d, priority: 15) } + + let_it_be(:issue_a) do + create( + :issue, + project: project_a, + labels: [priority3], + due_date: 1.day.ago, + milestone: milestone4, + relative_position: 1000 + ) + end + + let_it_be(:issue_b) do + create( + :issue, + :with_alert, + project: project_b, + discussion_locked: true, + due_date: 1.day.from_now, + relative_position: 3000 + ) + end - let_it_be(:issue_a) { create(:issue, project: project_a, labels: [priority3]) } - let_it_be(:issue_b) { create(:issue, :with_alert, project: project_b, discussion_locked: true) } let_it_be(:issue_c) do create( :issue, + :confidential, project: project_c, title: 'title matching issue plus', labels: [priority1], - milestone: late_milestone + milestone: milestone1, + due_date: 3.days.from_now, + relative_position: nil ) end - let_it_be(:issue_d) { create(:issue, :with_alert, project: project_d, discussion_locked: true, labels: [priority2]) } - let_it_be(:issue_e) { create(:issue, project: project_d, milestone: early_milestone) } + let_it_be(:issue_d) do + create( + :issue, + :with_alert, + project: project_d, + discussion_locked: true, + labels: [priority2], + milestone: milestone3, + relative_position: 5000 + ) + end - let(:issue_filter_params) { {} } + let_it_be(:issue_e) do + create( + :issue, + :confidential, + project: project_d, + milestone: milestone2, + due_date: 3.days.ago, + relative_position: nil, + labels: [priority2, priority4] + ) + end + let_it_be(:issues, reload: true) { [issue_a, issue_b, issue_c, issue_d, issue_e] } + + let(:issue_filter_params) { {} } + let(:current_user) { developer } let(:fields) do <<~QUERY - nodes { - #{all_graphql_fields_for('issues'.classify)} - } + nodes { id } QUERY end @@ -60,13 +107,16 @@ RSpec.describe 'getting an issue list at root level' do end end + # All new specs should be added to the shared example if the change also + # affects the `issues` query at the root level of the API. + # Shared example also used in spec/requests/api/graphql/project/issues_spec.rb it_behaves_like 'graphql issue list request spec' do - subject(:post_query) { post_graphql(query, current_user: current_user) } + let_it_be(:external_user) { create(:user) } + + let(:public_projects) { [project_a, project_c] } - let(:current_user) { developer } let(:another_user) { reporter } - let(:issues_data) { graphql_data['issues']['nodes'] } - let(:issue_ids) { graphql_dig_at(issues_data, :id) } + let(:issue_nodes_path) { %w[issues nodes] } # filters let(:expected_negated_assignee_issues) { [issue_b, issue_c, issue_d, issue_e] } @@ -77,12 +127,25 @@ RSpec.describe 'getting an issue list at root level' do let(:unlocked_discussion_issues) { [issue_a, issue_c, issue_e] } let(:search_title_term) { 'matching issue' } let(:title_search_issue) { issue_c } + let(:confidential_issues) { [issue_c, issue_e] } + let(:non_confidential_issues) { [issue_a, issue_b, issue_d] } + let(:public_non_confidential_issues) { [issue_a] } # sorting let(:data_path) { [:issues] } - let(:expected_severity_sorted_asc) { [issue_c, issue_a, issue_b, issue_e, issue_d] } - let(:expected_priority_sorted_asc) { [issue_e, issue_c, issue_d, issue_a, issue_b] } - let(:expected_priority_sorted_desc) { [issue_c, issue_e, issue_a, issue_d, issue_b] } + let(:expected_priority_sorted_asc) { [issue_c, issue_e, issue_d, issue_a, issue_b] } + let(:expected_priority_sorted_desc) { [issue_a, issue_d, issue_e, issue_c, issue_b] } + let(:expected_due_date_sorted_desc) { [issue_c, issue_b, issue_a, issue_e, issue_d] } + let(:expected_due_date_sorted_asc) { [issue_e, issue_a, issue_b, issue_c, issue_d] } + let(:expected_relative_position_sorted_asc) { [issue_a, issue_b, issue_d, issue_c, issue_e] } + let(:expected_label_priority_sorted_asc) { [issue_c, issue_e, issue_d, issue_a, issue_b] } + let(:expected_label_priority_sorted_desc) { [issue_a, issue_e, issue_d, issue_c, issue_b] } + let(:expected_milestone_sorted_asc) { [issue_c, issue_e, issue_d, issue_a, issue_b] } + let(:expected_milestone_sorted_desc) { [issue_a, issue_d, issue_e, issue_c, issue_b] } + + # N+1 queries + let(:same_project_issue1) { issue_d } + let(:same_project_issue2) { issue_e } before_all do issue_a.assignee_ids = developer.id @@ -90,12 +153,6 @@ RSpec.describe 'getting an issue list at root level' do create(:award_emoji, :upvote, user: developer, awardable: issue_a) create(:award_emoji, :upvote, user: developer, awardable: issue_c) - - # severity sorting - create(:issuable_severity, issue: issue_a, severity: :unknown) - create(:issuable_severity, issue: issue_b, severity: :low) - create(:issuable_severity, issue: issue_d, severity: :critical) - create(:issuable_severity, issue: issue_e, severity: :high) end def pagination_query(params) @@ -107,6 +164,27 @@ RSpec.describe 'getting an issue list at root level' do end end + context 'when fetching issues from multiple projects' do + it 'avoids N+1 queries' do + post_query # warm-up + + control = ActiveRecord::QueryRecorder.new { post_query } + + new_private_project = create(:project, :private).tap { |project| project.add_developer(current_user) } + create(:issue, project: new_private_project) + + expect { post_query }.not_to exceed_query_limit(control) + end + end + + def execute_query + post_query + end + + def post_query(request_user = current_user) + post_graphql(query, current_user: request_user) + end + def query(params = issue_filter_params) graphql_query_for( :issues, diff --git a/spec/requests/api/graphql/jobs_query_spec.rb b/spec/requests/api/graphql/jobs_query_spec.rb index 5907566be7f..0aea8e4c253 100644 --- a/spec/requests/api/graphql/jobs_query_spec.rb +++ b/spec/requests/api/graphql/jobs_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting job information' do +RSpec.describe 'getting job information', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:job) { create(:ci_build, :success, name: 'job1') } diff --git a/spec/requests/api/graphql/merge_request/merge_request_spec.rb b/spec/requests/api/graphql/merge_request/merge_request_spec.rb index d89f381753e..213697bacc1 100644 --- a/spec/requests/api/graphql/merge_request/merge_request_spec.rb +++ b/spec/requests/api/graphql/merge_request/merge_request_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.merge_request(id)' do +RSpec.describe 'Query.merge_request(id)', feature_category: :code_review do include GraphqlHelpers let_it_be(:project) { create(:project, :empty_repo) } diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb index 435e1b5b596..7d1850b1b93 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' -RSpec.describe 'getting project information' do +RSpec.describe 'getting project information', feature_category: :projects 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 72ec2b8e070..4dd47142c40 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' -RSpec.describe 'Getting Metrics Dashboard Annotations' do +RSpec.describe 'Getting Metrics Dashboard Annotations', feature_category: :metrics 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 1b84acff0e2..8db0844c6d7 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' -RSpec.describe 'Getting Metrics Dashboard' do +RSpec.describe 'Getting Metrics Dashboard', feature_category: :metrics do include GraphqlHelpers let_it_be(:current_user) { create(:user) } @@ -26,156 +26,73 @@ RSpec.describe 'Getting Metrics Dashboard' do ) end - context 'with metrics_dashboard_exhaustive_validations feature flag off' do + context 'for anonymous user' do before do - stub_feature_flags(metrics_dashboard_exhaustive_validations: false) + post_graphql(query, current_user: current_user) end - context 'for anonymous user' do - before do - post_graphql(query, current_user: current_user) - end - - context 'requested dashboard is available' do - let(:path) { 'config/prometheus/common_metrics.yml' } - - it_behaves_like 'a working graphql query' - - it 'returns nil' do - dashboard = graphql_data.dig('project', 'environments', 'nodes') - - expect(dashboard).to be_nil - end - end - end - - context 'for user with developer access' do - before do - project.add_developer(current_user) - post_graphql(query, current_user: current_user) - end - - context 'requested dashboard is available' do - let(:path) { 'config/prometheus/common_metrics.yml' } - - it_behaves_like 'a working graphql query' - - it 'returns metrics dashboard' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - - expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => nil) - end - - context 'invalid dashboard' do - let(:path) { '.gitlab/dashboards/metrics.yml' } - 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" => ["panel_groups: should be an array of panel_groups objects"]) - end - end - - context 'empty dashboard' do - let(:path) { '.gitlab/dashboards/metrics.yml' } - let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) } - - it 'returns metrics dashboard' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') + context 'requested dashboard is available' do + let(:path) { 'config/prometheus/common_metrics.yml' } - 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 - - context 'requested dashboard can not be found' do - let(:path) { 'config/prometheus/i_am_not_here.yml' } + it_behaves_like 'a working graphql query' - it_behaves_like 'a working graphql query' + it 'returns nil' do + dashboard = graphql_data.dig('project', 'environments', 'nodes') - it 'returns nil' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - - expect(dashboard).to be_nil - end + expect(dashboard).to be_nil end end end - context 'with metrics_dashboard_exhaustive_validations feature flag on' do + context 'for user with developer access' do before do - stub_feature_flags(metrics_dashboard_exhaustive_validations: true) + project.add_developer(current_user) + post_graphql(query, current_user: current_user) end - context 'for anonymous user' do - before do - post_graphql(query, current_user: current_user) - end - - context 'requested dashboard is available' do - let(:path) { 'config/prometheus/common_metrics.yml' } - - it_behaves_like 'a working graphql query' + context 'requested dashboard is available' do + let(:path) { 'config/prometheus/common_metrics.yml' } - it 'returns nil' do - dashboard = graphql_data.dig('project', 'environments', 'nodes') + it_behaves_like 'a working graphql query' - expect(dashboard).to be_nil - end - end - end + it 'returns metrics dashboard' do + dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - context 'for user with developer access' do - before do - project.add_developer(current_user) - post_graphql(query, current_user: current_user) + expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => nil) end - context 'requested dashboard is available' do - let(:path) { 'config/prometheus/common_metrics.yml' } - - it_behaves_like 'a working graphql query' + context 'invalid dashboard' do + let(:path) { '.gitlab/dashboards/metrics.yml' } + 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" => nil) - end - - context 'invalid dashboard' do - let(:path) { '.gitlab/dashboards/metrics.yml' } - 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" => ["root is missing required keys: panel_groups"]) - end + expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"]) end + end - context 'empty dashboard' do - let(:path) { '.gitlab/dashboards/metrics.yml' } - let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) } + context 'empty dashboard' do + let(:path) { '.gitlab/dashboards/metrics.yml' } + let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) } - it 'returns metrics dashboard' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') + it 'returns metrics dashboard' do + dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["root is missing required keys: dashboard, panel_groups"]) - end + 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 - context 'requested dashboard can not be found' do - let(:path) { 'config/prometheus/i_am_not_here.yml' } + context 'requested dashboard can not be found' do + let(:path) { 'config/prometheus/i_am_not_here.yml' } - it_behaves_like 'a working graphql query' + it_behaves_like 'a working graphql query' - it 'returns nil' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') + it 'returns nil' do + dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - expect(dashboard).to be_nil - end + expect(dashboard).to be_nil end end end diff --git a/spec/requests/api/graphql/milestone_spec.rb b/spec/requests/api/graphql/milestone_spec.rb index 78e7ec39ee3..2cea9fd0408 100644 --- a/spec/requests/api/graphql/milestone_spec.rb +++ b/spec/requests/api/graphql/milestone_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Querying a Milestone' do +RSpec.describe 'Querying a Milestone', feature_category: :team_planning do include GraphqlHelpers let_it_be(:group) { create(:group, :public) } diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb index f79bac6ae3b..4d615d3eaa4 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' -RSpec.describe 'Multiplexed queries' do +RSpec.describe 'Multiplexed queries', feature_category: :not_owned 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 f992e46879f..64ea6d32f5f 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' -RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues do +RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_category: :not_owned 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 f637ca98353..fbe6d95dfff 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' -RSpec.describe 'Create an alert issue from an alert' do +RSpec.describe 'Create an alert issue from an alert', feature_category: :incident_management 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 fcef7b4e3ec..935856814c4 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' -RSpec.describe 'Setting assignees of an alert' do +RSpec.describe 'Setting assignees of an alert', feature_category: :incident_management 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 index 48307964345..570324a3126 100644 --- 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 @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creating a todo for the alert' do +RSpec.describe 'Creating a todo for the alert', feature_category: :incident_management do include GraphqlHelpers let_it_be(:user) { create(:user) } 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 802d8d6c5a1..6537747850c 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' -RSpec.describe 'Setting the status of an alert' do +RSpec.describe 'Setting the status of an alert', feature_category: :incident_management do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/alert_management/http_integration/create_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/create_spec.rb index ff93da2153f..187c88363c6 100644 --- a/spec/requests/api/graphql/mutations/alert_management/http_integration/create_spec.rb +++ b/spec/requests/api/graphql/mutations/alert_management/http_integration/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creating a new HTTP Integration' do +RSpec.describe 'Creating a new HTTP Integration', feature_category: :integrations do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/alert_management/http_integration/destroy_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/destroy_spec.rb index 1ecb5c76b57..1c77c71daba 100644 --- a/spec/requests/api/graphql/mutations/alert_management/http_integration/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/alert_management/http_integration/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Removing an HTTP Integration' do +RSpec.describe 'Removing an HTTP Integration', feature_category: :integrations do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/alert_management/http_integration/reset_token_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/reset_token_spec.rb index badd9412589..427277dd540 100644 --- a/spec/requests/api/graphql/mutations/alert_management/http_integration/reset_token_spec.rb +++ b/spec/requests/api/graphql/mutations/alert_management/http_integration/reset_token_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Resetting a token on an existing HTTP Integration' do +RSpec.describe 'Resetting a token on an existing HTTP Integration', feature_category: :integrations do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb index 18cbb7d8b00..a9d189d564d 100644 --- a/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb +++ b/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Updating an existing HTTP Integration' do +RSpec.describe 'Updating an existing HTTP Integration', feature_category: :integrations do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/create_spec.rb b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/create_spec.rb index 4c359d9b357..3dee7f50af3 100644 --- a/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/create_spec.rb +++ b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creating a new Prometheus Integration' do +RSpec.describe 'Creating a new Prometheus Integration', feature_category: :incident_management do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb index 31053c50cac..15127843b95 100644 --- a/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb +++ b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/reset_token_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Resetting a token on an existing Prometheus Integration' do +RSpec.describe 'Resetting a token on an existing Prometheus Integration', feature_category: :incident_management do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/update_spec.rb b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/update_spec.rb index ad26ec118d7..63e95f4513b 100644 --- a/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/update_spec.rb +++ b/spec/requests/api/graphql/mutations/alert_management/prometheus_integration/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Updating an existing Prometheus Integration' do +RSpec.describe 'Updating an existing Prometheus Integration', feature_category: :incident_management 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 3879e58cecf..fdbff0f93cd 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' -RSpec.describe 'Adding an AwardEmoji' do +RSpec.describe 'Adding an AwardEmoji', feature_category: :not_owned do include GraphqlHelpers let_it_be(:current_user) { create(:user) } 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 e81621209fb..e200bfc2d18 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' -RSpec.describe 'Removing an AwardEmoji' do +RSpec.describe 'Removing an AwardEmoji', feature_category: :not_owned do include GraphqlHelpers let(:current_user) { create(: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 b151da72b55..6dba2b58357 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' -RSpec.describe 'Toggling an AwardEmoji' do +RSpec.describe 'Toggling an AwardEmoji', feature_category: :not_owned do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/boards/create_spec.rb b/spec/requests/api/graphql/mutations/boards/create_spec.rb index ca848c0c92f..10eb12f277f 100644 --- a/spec/requests/api/graphql/mutations/boards/create_spec.rb +++ b/spec/requests/api/graphql/mutations/boards/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::Boards::Create do +RSpec.describe Mutations::Boards::Create, feature_category: :team_planning do let_it_be(:parent) { create(:project) } let_it_be(:current_user, reload: true) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/boards/destroy_spec.rb b/spec/requests/api/graphql/mutations/boards/destroy_spec.rb index 7620da3e7e0..44924159137 100644 --- a/spec/requests/api/graphql/mutations/boards/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/boards/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::Boards::Destroy do +RSpec.describe Mutations::Boards::Destroy, feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user, reload: true) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb b/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb index 06093e9f7c2..df64caa1cfb 100644 --- a/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb +++ b/spec/requests/api/graphql/mutations/boards/issues/issue_move_list_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Reposition and move issue within board lists' do +RSpec.describe 'Reposition and move issue within board lists', feature_category: :team_planning do include GraphqlHelpers let_it_be(:group) { create(:group, :private) } diff --git a/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb index fec9a8c6307..f5381be2741 100644 --- a/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb +++ b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Create a label or backlog board list' do +RSpec.describe 'Create a label or backlog board list', feature_category: :team_planning do let_it_be(:group) { create(:group, :private) } let_it_be(:board) { create(:board, group: group) } diff --git a/spec/requests/api/graphql/mutations/boards/lists/destroy_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/destroy_spec.rb index 83309ead352..b37b1a7b935 100644 --- a/spec/requests/api/graphql/mutations/boards/lists/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/boards/lists/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::Boards::Lists::Destroy do +RSpec.describe Mutations::Boards::Lists::Destroy, feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user, reload: true) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb index c7885879a9d..6a7edcd7349 100644 --- a/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb +++ b/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Update of an existing board list' do +RSpec.describe 'Update of an existing board list', feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/branches/create_spec.rb b/spec/requests/api/graphql/mutations/branches/create_spec.rb index 9ee2f41e8fc..32512e2ee1b 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' -RSpec.describe 'Creation of a new branch' do +RSpec.describe 'Creation of a new branch', feature_category: :source_code_management do include GraphqlHelpers let_it_be(:group) { create(:group, :public) } diff --git a/spec/requests/api/graphql/mutations/ci/job/artifacts_destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/job/artifacts_destroy_spec.rb index bdad80995ea..6cdf8788957 100644 --- a/spec/requests/api/graphql/mutations/ci/job/artifacts_destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job/artifacts_destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'JobArtifactsDestroy' do +RSpec.describe 'JobArtifactsDestroy', feature_category: :build_artifacts do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/job/destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/job/destroy_spec.rb index 5855eb6bb51..88dfec41d36 100644 --- a/spec/requests/api/graphql/mutations/ci/job/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'JobArtifactsDestroy' do +RSpec.describe 'JobArtifactsDestroy', feature_category: :build_artifacts do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/job_artifact/destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/job_artifact/destroy_spec.rb index a5ec9ea343d..ac3592130b8 100644 --- a/spec/requests/api/graphql/mutations/ci/job_artifact/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_artifact/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'ArtifactDestroy' do +RSpec.describe 'ArtifactDestroy', feature_category: :build_artifacts do include GraphqlHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb b/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb index ee0f0a9bccb..468a9e57f56 100644 --- a/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_cancel_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "JobCancel" do +RSpec.describe "JobCancel", feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/job_play_spec.rb b/spec/requests/api/graphql/mutations/ci/job_play_spec.rb index 0874e225259..014a5e0f1c7 100644 --- a/spec/requests/api/graphql/mutations/ci/job_play_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_play_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'JobPlay' do +RSpec.describe 'JobPlay', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/job_retry_spec.rb b/spec/requests/api/graphql/mutations/ci/job_retry_spec.rb index 8cf559a372a..e49ee6f3163 100644 --- a/spec/requests/api/graphql/mutations/ci/job_retry_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_retry_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'JobRetry' do +RSpec.describe 'JobRetry', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb b/spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb index b2f84ab2869..490716ddbe2 100644 --- a/spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'CiJobTokenScopeAddProject' do +RSpec.describe 'CiJobTokenScopeAddProject', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project, ci_outbound_job_token_scope_enabled: true).tap(&:save!) } @@ -60,7 +60,7 @@ RSpec.describe 'CiJobTokenScopeAddProject' do post_graphql_mutation(mutation, current_user: current_user) expect(response).to have_gitlab_http_status(:success) expect(mutation_response.dig('ciJobTokenScope', 'projects', 'nodes')).not_to be_empty - end.to change { Ci::JobToken::Scope.new(project).includes?(target_project) }.from(false).to(true) + end.to change { Ci::JobToken::Scope.new(project).allows?(target_project) }.from(false).to(true) end context 'when invalid target project is provided' do diff --git a/spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb b/spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb index 2b0adf89f40..607c6bd85c2 100644 --- a/spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'CiJobTokenScopeRemoveProject' do +RSpec.describe 'CiJobTokenScopeRemoveProject', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project, ci_outbound_job_token_scope_enabled: true).tap(&:save!) } @@ -66,7 +66,7 @@ RSpec.describe 'CiJobTokenScopeRemoveProject' do post_graphql_mutation(mutation, current_user: current_user) expect(response).to have_gitlab_http_status(:success) expect(mutation_response.dig('ciJobTokenScope', 'projects', 'nodes')).not_to be_empty - end.to change { Ci::JobToken::Scope.new(project).includes?(target_project) }.from(true).to(false) + end.to change { Ci::JobToken::Scope.new(project).allows?(target_project) }.from(true).to(false) end context 'when invalid target project is provided' do diff --git a/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb b/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb index 4ddc019a2b5..6868b0ea279 100644 --- a/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/job_unschedule_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'JobUnschedule' do +RSpec.describe 'JobUnschedule', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb index 6ec1b7ce9b6..8c1359384ed 100644 --- a/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'PipelineCancel' do +RSpec.describe 'PipelineCancel', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb index 7abd5ca8772..9ddfaf83d34 100644 --- a/spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'PipelineDestroy' do +RSpec.describe 'PipelineDestroy', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb index f6acf29c321..e7edc86bea0 100644 --- a/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'PipelineRetry' do +RSpec.describe 'PipelineRetry', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_schedule_create_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_create_spec.rb new file mode 100644 index 00000000000..4a45d255d99 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_create_spec.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'PipelineSchedulecreate' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository) } + + let(:mutation) do + variables = { + project_path: project.full_path, + **pipeline_schedule_parameters + } + + graphql_mutation( + :pipeline_schedule_create, + variables, + <<-QL + pipelineSchedule { + id + description + cron + refForDisplay + active + cronTimezone + variables { + nodes { + key + value + } + } + owner { + id + } + } + errors + QL + ) + end + + let(:pipeline_schedule_parameters) do + { + description: 'created_desc', + cron: '0 1 * * *', + cronTimezone: 'UTC', + ref: 'patch-x', + active: true, + variables: [ + { key: 'AAA', value: "AAA123", variableType: 'ENV_VAR' } + ] + } + end + + let(:mutation_response) { graphql_mutation_response(:pipeline_schedule_create) } + + context 'when unauthorized' do + it 'returns an error' do + post_graphql_mutation(mutation, current_user: user) + + expect(graphql_errors).not_to be_empty + expect(graphql_errors[0]['message']) + .to eq( + "The resource that you are attempting to access does not exist " \ + "or you don't have permission to perform this action" + ) + end + end + + context 'when authorized' do + before do + project.add_developer(user) + end + + context 'when success' do + it do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + + expect(mutation_response['pipelineSchedule']['owner']['id']).to eq(user.to_global_id.to_s) + + %w[description cron cronTimezone active].each do |key| + expect(mutation_response['pipelineSchedule'][key]).to eq(pipeline_schedule_parameters[key.to_sym]) + end + + expect(mutation_response['pipelineSchedule']['refForDisplay']).to eq(pipeline_schedule_parameters[:ref]) + + expect(mutation_response['pipelineSchedule']['variables']['nodes'][0]['key']).to eq('AAA') + expect(mutation_response['pipelineSchedule']['variables']['nodes'][0]['value']).to eq('AAA123') + + expect(mutation_response['pipelineSchedule']['owner']['id']).to eq(user.to_global_id.to_s) + + expect(mutation_response['errors']).to eq([]) + end + end + + context 'when failure' do + context 'when params are invalid' do + let(:pipeline_schedule_parameters) do + { + description: 'some description', + cron: 'abc', + cronTimezone: 'cCc', + ref: 'asd', + active: true, + variables: [] + } + end + + it do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + + expect(mutation_response['errors']) + .to match_array( + ["Cron is invalid syntax", "Cron timezone is invalid syntax"] + ) + end + end + + context 'when variables have duplicate name' do + before do + pipeline_schedule_parameters.merge!( + { + variables: [ + { key: 'AAA', value: "AAA123", variableType: 'ENV_VAR' }, + { key: 'AAA', value: "AAA123", variableType: 'ENV_VAR' } + ] + } + ) + end + + it 'returns error' do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + + expect(mutation_response['errors']) + .to match_array( + [ + "Variables have duplicate values (AAA)" + ] + ) + end + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_schedule_delete_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_delete_spec.rb index b197d223463..b846ff0aec8 100644 --- a/spec/requests/api/graphql/mutations/ci/pipeline_schedule_delete_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_delete_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'PipelineScheduleDelete' do +RSpec.describe 'PipelineScheduleDelete', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_schedule_play_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_play_spec.rb new file mode 100644 index 00000000000..0e43fa024f3 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_play_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'PipelineSchedulePlay', feature_category: :continuious_integration do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:pipeline_schedule) do + create( + :ci_pipeline_schedule, + :every_minute, + project: project, + owner: user + ) + end + + let(:mutation) do + graphql_mutation( + :pipeline_schedule_play, + { id: pipeline_schedule.to_global_id.to_s }, + <<-QL + pipelineSchedule { id, nextRunAt } + errors + QL + ) + end + + let(:mutation_response) { graphql_mutation_response(:pipeline_schedule_play) } + + context 'when unauthorized' do + it 'returns an error' do + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).not_to be_empty + expect(graphql_errors[0]['message']) + .to eq( + "The resource that you are attempting to access does not exist " \ + "or you don't have permission to perform this action" + ) + end + end + + context 'when authorized' do + before do + project.add_maintainer(user) + pipeline_schedule.update_columns(next_run_at: 2.hours.ago) + end + + context 'when mutation succeeds' do + it do + post_graphql_mutation(mutation, current_user: user) + + expect(mutation_response['pipelineSchedule']['id']).to include(pipeline_schedule.id.to_s) + new_next_run_at = DateTime.parse(mutation_response['pipelineSchedule']['nextRunAt']) + expect(new_next_run_at).not_to eq(pipeline_schedule.next_run_at) + expect(new_next_run_at).to eq(pipeline_schedule.reset.next_run_at) + expect(mutation_response['errors']).to eq([]) + end + end + + context 'when mutation fails' do + before do + allow(RunPipelineScheduleWorker).to receive(:perform_async).and_return(nil) + end + + it do + expect(RunPipelineScheduleWorker) + .to receive(:perform_async) + .with(pipeline_schedule.id, user.id) + + post_graphql_mutation(mutation, current_user: user) + + expect(mutation_response['pipelineSchedule']).to be_nil + expect(mutation_response['errors']).to match_array(['Unable to schedule a pipeline to run immediately.']) + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb index 8dfbf20d00b..2d1f1565a73 100644 --- a/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'PipelineScheduleTakeOwnership' do +RSpec.describe 'PipelineScheduleTakeOwnership', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb b/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb index c808cf5ede9..7a6ee7c2ecc 100644 --- a/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'ProjectCiCdSettingsUpdate' do +RSpec.describe 'ProjectCiCdSettingsUpdate', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) do diff --git a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb index 54e63df96a6..752242c3ab3 100644 --- a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'RunnersRegistrationTokenReset' do +RSpec.describe 'RunnersRegistrationTokenReset', feature_category: :runner_fleet do include GraphqlHelpers let(:mutation) { graphql_mutation(:runners_registration_token_reset, input) } diff --git a/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb b/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb index aac8eb22771..f544cef8864 100644 --- a/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb +++ b/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Create a new cluster agent token' do +RSpec.describe 'Create a new cluster agent token', feature_category: :kubernetes_management do include GraphqlHelpers let_it_be(:cluster_agent) { create(:cluster_agent) } diff --git a/spec/requests/api/graphql/mutations/clusters/agents/create_spec.rb b/spec/requests/api/graphql/mutations/clusters/agents/create_spec.rb index c2ef2362d66..66e6c5cc629 100644 --- a/spec/requests/api/graphql/mutations/clusters/agents/create_spec.rb +++ b/spec/requests/api/graphql/mutations/clusters/agents/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Create a new cluster agent' do +RSpec.describe 'Create a new cluster agent', feature_category: :kubernetes_management do include GraphqlHelpers let(:project) { create(:project, :public, :repository) } diff --git a/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb index 4891e64aab8..27a566dfb8c 100644 --- a/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Delete a cluster agent' do +RSpec.describe 'Delete a cluster agent', feature_category: :kubernetes_management do include GraphqlHelpers let(:cluster_agent) { create(:cluster_agent) } diff --git a/spec/requests/api/graphql/mutations/commits/create_spec.rb b/spec/requests/api/graphql/mutations/commits/create_spec.rb index 619cba99c4e..e298d8284c6 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' -RSpec.describe 'Creation of a new commit' do +RSpec.describe 'Creation of a new commit', feature_category: :source_code_management 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 ca7c1b2ce5f..97e2a5d0bf7 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' -RSpec.describe 'Updating the container expiration policy' do +RSpec.describe 'Updating the container expiration policy', feature_category: :container_registry do include GraphqlHelpers using RSpec::Parameterized::TableSyntax diff --git a/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb index 5a27d39ecbc..8b76c19cda6 100644 --- a/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Destroying a container repository' do +RSpec.describe 'Destroying a container repository', feature_category: :container_registry do using RSpec::Parameterized::TableSyntax include GraphqlHelpers @@ -80,25 +80,6 @@ RSpec.describe 'Destroying a container repository' do it_behaves_like params[:shared_examples_name] end - - context 'with container_registry_delete_repository_with_cron_worker disabled' do - before do - project.add_maintainer(user) - stub_feature_flags(container_registry_delete_repository_with_cron_worker: false) - end - - it 'enqueues a removal job' do - expect(::Packages::CreateEventService) - .to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original - expect(DeleteContainerRepositoryWorker) - .to receive(:perform_async).with(user.id, container_repository.id) - - expect { subject }.to change { ::Packages::Event.count }.by(1) - - expect(container_repository_mutation_response).to match_schema('graphql/container_repository') - expect(container_repository_mutation_response['status']).to eq('DELETE_SCHEDULED') - end - end end context 'with invalid id' do diff --git a/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb b/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb index ef00f45ef18..9e07a831076 100644 --- a/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb +++ b/spec/requests/api/graphql/mutations/container_repository/destroy_tags_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Destroying a container repository tags' do +RSpec.describe 'Destroying a container repository tags', feature_category: :container_registry do include_context 'container repository delete tags service shared context' using RSpec::Parameterized::TableSyntax diff --git a/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb b/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb index 66facdebe78..ea2ce8a13e2 100644 --- a/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb +++ b/spec/requests/api/graphql/mutations/custom_emoji/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creation of a new Custom Emoji' do +RSpec.describe 'Creation of a new Custom Emoji', feature_category: :not_owned do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb b/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb index 7d25206e617..ad7a043909a 100644 --- a/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/custom_emoji/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Deletion of custom emoji' do +RSpec.describe 'Deletion of custom emoji', feature_category: :not_owned do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb b/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb index 9eb13e534ac..5d5696d3f66 100644 --- a/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb +++ b/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Updating the dependency proxy group settings' do +RSpec.describe 'Updating the dependency proxy group settings', feature_category: :dependency_proxy do include GraphqlHelpers using RSpec::Parameterized::TableSyntax diff --git a/spec/requests/api/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb b/spec/requests/api/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb index 31ba7ecdf0e..66ee17f356c 100644 --- a/spec/requests/api/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb +++ b/spec/requests/api/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Updating the dependency proxy image ttl policy' do +RSpec.describe 'Updating the dependency proxy image ttl policy', feature_category: :dependency_proxy do include GraphqlHelpers using RSpec::Parameterized::TableSyntax 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 e2ab08b301b..7ea32ae6d19 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" -RSpec.describe "deleting designs" do +RSpec.describe "deleting designs", feature_category: :design_management do include GraphqlHelpers include DesignManagementTestHelpers diff --git a/spec/requests/api/graphql/mutations/design_management/move_spec.rb b/spec/requests/api/graphql/mutations/design_management/move_spec.rb index dd121ec733e..27b5259c56b 100644 --- a/spec/requests/api/graphql/mutations/design_management/move_spec.rb +++ b/spec/requests/api/graphql/mutations/design_management/move_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "spec_helper" -RSpec.describe "moving designs" do +RSpec.describe "moving designs", feature_category: :design_management 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 d3e6c689a59..9b42b32c150 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" -RSpec.describe "uploading designs" do +RSpec.describe "uploading designs", feature_category: :design_management 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 632a934cd95..16d3bbb6518 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' -RSpec.describe 'Toggling the resolve status of a discussion' do +RSpec.describe 'Toggling the resolve status of a discussion', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project, :public, :repository) } diff --git a/spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb b/spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb index 3771ae0746e..0e9317a4879 100644 --- a/spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb +++ b/spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Update Environment Canary Ingress', :clean_gitlab_redis_cache do +RSpec.describe 'Update Environment Canary Ingress', :clean_gitlab_redis_cache, feature_category: :deployment_management do include GraphqlHelpers include KubernetesHelpers diff --git a/spec/requests/api/graphql/mutations/groups/update_spec.rb b/spec/requests/api/graphql/mutations/groups/update_spec.rb index b9dfb8e37ab..ea3d42a4463 100644 --- a/spec/requests/api/graphql/mutations/groups/update_spec.rb +++ b/spec/requests/api/graphql/mutations/groups/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'GroupUpdate' do +RSpec.describe 'GroupUpdate', feature_category: :subgroups do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb index fc3b666dd3d..49cee4f6801 100644 --- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creating an incident timeline event' do +RSpec.describe 'Creating an incident timeline event', feature_category: :incident_management do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb index 85208869ad9..6e1a7b36736 100644 --- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Removing an incident timeline event' do +RSpec.describe 'Removing an incident timeline event', feature_category: :incident_management do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb index 62eeecb3fb7..ca9557b3183 100644 --- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Promote an incident timeline event from a comment' do +RSpec.describe 'Promote an incident timeline event from a comment', feature_category: :incident_management do include GraphqlHelpers include NotesHelper diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb index 542d51b990f..163c689e399 100644 --- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb @@ -2,24 +2,36 @@ require 'spec_helper' -RSpec.describe 'Updating an incident timeline event' do +RSpec.describe 'Updating an incident timeline event', feature_category: :incident_management do include GraphqlHelpers let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:incident) { create(:incident, project: project) } + let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') } + let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') } let_it_be_with_reload(:timeline_event) do create(:incident_management_timeline_event, incident: incident, project: project) end + # Pre-attach a tag to the event + let_it_be(:tag_link1) do + create(:incident_management_timeline_event_tag_link, + timeline_event: timeline_event, + timeline_event_tag: tag1 + ) + end + let(:occurred_at) { 1.minute.ago.iso8601 } let(:note) { 'Updated note' } + let(:tag_names) { [] } let(:variables) do { id: timeline_event.to_global_id.to_s, note: note, - occurred_at: occurred_at + occurred_at: occurred_at, + timeline_event_tag_names: tag_names } end @@ -33,6 +45,7 @@ RSpec.describe 'Updating an incident timeline event' do author { id username } updatedByUser { id username } incident { id title } + timelineEventTags { nodes { name } } note noteHtml occurredAt @@ -71,6 +84,9 @@ RSpec.describe 'Updating an incident timeline event' do 'id' => incident.to_global_id.to_s, 'title' => incident.title }, + 'timelineEventTags' => { + 'nodes' => [] + }, 'note' => note, 'noteHtml' => timeline_event.note_html, 'occurredAt' => occurred_at, @@ -85,4 +101,27 @@ RSpec.describe 'Updating an incident timeline event' do it_behaves_like 'timeline event mutation responds with validation error', error_message: 'Timeline text is too long (maximum is 280 characters)' end + + context 'when timeline event tag names are passed' do + context 'when tags exist' do + let(:tag_names) { [tag2.name] } + + it 'removes tag1 and adds tag2' do + post_graphql_mutation(mutation, current_user: user) + + timeline_event_response = mutation_response['timelineEvent'] + tag_names = timeline_event_response['timelineEventTags']['nodes'] + + expect(response).to have_gitlab_http_status(:success) + expect(tag_names).to contain_exactly({ "name" => tag2.name }) + end + end + + context 'when tags do not exist' do + let(:tag_names) { ['some other tag'] } + + it_behaves_like 'timeline event mutation responds with validation error', + error_message: "Following tags don't exist: [\"some other tag\"]" + end + end end diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event_tag/create_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event_tag/create_spec.rb index 7476499d9da..b37a5331421 100644 --- a/spec/requests/api/graphql/mutations/incident_management/timeline_event_tag/create_spec.rb +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event_tag/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creating a timeline event tag' do +RSpec.describe 'Creating a timeline event tag', feature_category: :incident_management do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/issues/create_spec.rb b/spec/requests/api/graphql/mutations/issues/create_spec.rb index a489b7424e8..d2d2f0014d6 100644 --- a/spec/requests/api/graphql/mutations/issues/create_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Create an issue' do +RSpec.describe 'Create an issue', feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb b/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb new file mode 100644 index 00000000000..85e21952f47 --- /dev/null +++ b/spec/requests/api/graphql/mutations/issues/link_alerts_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Link alerts to an incident', feature_category: :incident_management do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:linked_alert) { create(:alert_management_alert, project: project) } + let_it_be(:alert1) { create(:alert_management_alert, project: project) } + let_it_be(:alert2) { create(:alert_management_alert, project: project) } + let_it_be(:incident) { create(:incident, project: project, alert_management_alerts: [linked_alert]) } + + let(:mutation) do + variables = { + project_path: project.full_path, + iid: incident.iid.to_s, + alert_references: [alert1.to_reference, alert2.details_url] + } + + graphql_mutation(:issue_link_alerts, variables, + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + alertManagementAlerts { + nodes { + iid + } + } + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:issue_link_alerts) + end + + context 'when the user is not allowed to update the incident' do + it 'returns an error' do + error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to include(a_hash_including('message' => error)) + end + end + + context 'when the user is allowed to update the incident' do + before do + project.add_developer(user) + end + + it 'links alerts to the incident' do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expected_response = [linked_alert, alert1, alert2].map { |a| { 'iid' => a.iid.to_s } } + expect(mutation_response.dig('issue', 'alertManagementAlerts', 'nodes')).to match_array(expected_response) + end + end +end diff --git a/spec/requests/api/graphql/mutations/issues/move_spec.rb b/spec/requests/api/graphql/mutations/issues/move_spec.rb index 20ed16879f6..7d9579067b6 100644 --- a/spec/requests/api/graphql/mutations/issues/move_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/move_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Moving an issue' do +RSpec.describe 'Moving an issue', feature_category: :team_planning do include GraphqlHelpers let_it_be(:user) { create(:user) } 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 12ab504da14..c5e6901d8f8 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' -RSpec.describe 'Setting an issue as confidential' do +RSpec.describe 'Setting an issue as confidential', feature_category: :team_planning do include GraphqlHelpers let(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb index 395a490bfc3..9fce5f8497f 100644 --- a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting issues crm contacts' do +RSpec.describe 'Setting issues crm contacts', feature_category: :service_desk do include GraphqlHelpers let_it_be(: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 8e223b6fdaf..1a5a64e4196 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' -RSpec.describe 'Setting Due Date of an issue' do +RSpec.describe 'Setting Due Date of an issue', feature_category: :team_planning do include GraphqlHelpers let(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/issues/set_escalation_status_spec.rb b/spec/requests/api/graphql/mutations/issues/set_escalation_status_spec.rb index a81364d37b2..8fc3ad4236d 100644 --- a/spec/requests/api/graphql/mutations/issues/set_escalation_status_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_escalation_status_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting the escalation status of an incident' do +RSpec.describe 'Setting the escalation status of an incident', feature_category: :incident_management do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb index 435ed0f9eb2..a8025894b1e 100644 --- a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting an issue as locked' do +RSpec.describe 'Setting an issue as locked', feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb index cd9d695bd2c..77262c7f64f 100644 --- a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting severity level of an incident' do +RSpec.describe 'Setting severity level of an incident', feature_category: :incident_management do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/issues/set_subscription_spec.rb b/spec/requests/api/graphql/mutations/issues/set_subscription_spec.rb index 1edc1e0553b..6c8e5b1d15d 100644 --- a/spec/requests/api/graphql/mutations/issues/set_subscription_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/set_subscription_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting subscribed status of an issue' do +RSpec.describe 'Setting subscribed status of an issue', feature_category: :team_planning do include GraphqlHelpers it_behaves_like 'a subscribable resource api' do diff --git a/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb b/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb new file mode 100644 index 00000000000..7f6f968b1dd --- /dev/null +++ b/spec/requests/api/graphql/mutations/issues/unlink_alerts_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Unlink alert from an incident', feature_category: :incident_management do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:another_project) { create(:project) } + let_it_be(:internal_alert) { create(:alert_management_alert, project: project) } + let_it_be(:external_alert) { create(:alert_management_alert, project: another_project) } + let_it_be(:incident) do + create(:incident, project: project, alert_management_alerts: [internal_alert, external_alert]) + end + + let(:mutation) do + variables = { + project_path: project.full_path, + iid: incident.iid.to_s, + alert_id: alert_to_unlink.to_global_id.to_s + } + + graphql_mutation(:issue_unlink_alert, variables, + <<-QL.strip_heredoc + clientMutationId + errors + issue { + iid + alertManagementAlerts { + nodes { + id + } + } + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:issue_unlink_alert) + end + + context 'when the user is not allowed to update the incident' do + let(:alert_to_unlink) { internal_alert } + + it 'returns an error' do + error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to include(a_hash_including('message' => error)) + end + end + + context 'when the user is allowed to update the incident' do + before_all do + project.add_developer(user) + end + + shared_examples 'unlinking' do + it 'unlinks the alert from the incident', :aggregate_failures do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expected_response = visible_remainded_alerts.map { |a| { 'id' => a.to_global_id.to_s } } + expect(mutation_response.dig('issue', 'alertManagementAlerts', 'nodes')).to match_array(expected_response) + + expect(incident.reload.alert_management_alerts).to match_array(actual_remainded_alerts) + end + end + + context 'when the alert is internal' do + let(:alert_to_unlink) { internal_alert } + let(:actual_remainded_alerts) { [external_alert] } + let(:visible_remainded_alerts) { [] } # The user cannot fetch external alerts without reading permissions + + it_behaves_like 'unlinking' + end + + context 'when the alert is external' do + let(:alert_to_unlink) { external_alert } + let(:actual_remainded_alerts) { [internal_alert] } + let(:visible_remainded_alerts) { [internal_alert] } + + it_behaves_like 'unlinking' + end + end +end diff --git a/spec/requests/api/graphql/mutations/issues/update_spec.rb b/spec/requests/api/graphql/mutations/issues/update_spec.rb index f38deb426b1..e51c057c182 100644 --- a/spec/requests/api/graphql/mutations/issues/update_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Update of an existing issue' do +RSpec.describe 'Update of an existing issue', feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } 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 b438e1ba881..ab15aa97680 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' -RSpec.describe 'Importing Jira Users' do +RSpec.describe 'Importing Jira Users', feature_category: :integrations do include JiraIntegrationHelpers 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 1508ba31e37..a864bc88afc 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' -RSpec.describe 'Starting a Jira Import' do +RSpec.describe 'Starting a Jira Import', feature_category: :integrations do include JiraIntegrationHelpers include GraphqlHelpers diff --git a/spec/requests/api/graphql/mutations/labels/create_spec.rb b/spec/requests/api/graphql/mutations/labels/create_spec.rb index d19411f6c1d..607e20af977 100644 --- a/spec/requests/api/graphql/mutations/labels/create_spec.rb +++ b/spec/requests/api/graphql/mutations/labels/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::Labels::Create do +RSpec.describe Mutations::Labels::Create, feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } 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 3a4508489a1..c954fd50cc4 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' -RSpec.describe 'Creation of a new merge request' do +RSpec.describe 'Creation of a new merge request', feature_category: :code_review do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb index 2e4f35cbcde..c41161eff2b 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/reviewer_rereview_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting assignees of a merge request' do +RSpec.describe 'Setting assignees of a merge request', feature_category: :code_review do include GraphqlHelpers let(: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 8cec5867aca..364d13291db 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' -RSpec.describe 'Setting assignees of a merge request', :assume_throttled do +RSpec.describe 'Setting assignees of a merge request', :assume_throttled, feature_category: :code_review do include GraphqlHelpers let_it_be(:project) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb index bea2365eaa6..b48a94fbeb9 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting Draft status of a merge request' do +RSpec.describe 'Setting Draft status of a merge request', feature_category: :code_review do include GraphqlHelpers let(:current_user) { create(:user) } @@ -64,7 +64,7 @@ RSpec.describe 'Setting Draft status of a merge request' 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(/draft\:/) + expect(mutation_response['mergeRequest']['title']).not_to start_with(/draft:/) end it 'unmarks the merge request as `Draft`' do 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 a1a35bc1dcc..d88982c508c 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' -RSpec.describe 'Setting locked status of a merge request' do +RSpec.describe 'Setting locked status of a merge request', feature_category: :code_review 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 d7e2602bd0a..a0f0e45d1fc 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' -RSpec.describe 'Setting milestone of a merge request' do +RSpec.describe 'Setting milestone of a merge request', feature_category: :code_review do include GraphqlHelpers let(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb index be786256ef2..a5be2a95c8b 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_reviewers_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Setting reviewers of a merge request', :assume_throttled do +RSpec.describe 'Setting reviewers of a merge request', :assume_throttled, feature_category: :code_review do include GraphqlHelpers let_it_be(:project) { create(:project, :repository) } 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 d90faa605c0..daf1f529847 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' -RSpec.describe 'Setting subscribed status of a merge request' do +RSpec.describe 'Setting subscribed status of a merge request', feature_category: :code_review do include GraphqlHelpers it_behaves_like 'a subscribable resource api' do 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 9ef443af76a..bce57b47aab 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' -RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create do +RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create, feature_category: :metrics 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 b956734068c..f505dc25dc0 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' -RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete do +RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete, feature_category: :metrics do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb index 567d8799d93..f4f4f34fe29 100644 --- a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb +++ b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Updating the package settings' do +RSpec.describe 'Updating the package settings', feature_category: :package_registry do include GraphqlHelpers using RSpec::Parameterized::TableSyntax 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 a432fb17a70..3cf78230a4c 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' -RSpec.describe 'Adding a DiffNote' do +RSpec.describe 'Adding a DiffNote', feature_category: :team_planning 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 8f2438cb741..0ce239d9ca5 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' -RSpec.describe 'Adding an image DiffNote' do +RSpec.describe 'Adding an image DiffNote', feature_category: :team_planning 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 9c3842db31a..00e25909746 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' -RSpec.describe 'Adding a Note' do +RSpec.describe 'Adding a Note', feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } @@ -102,6 +102,35 @@ RSpec.describe 'Adding a Note' do it_behaves_like 'a Note mutation with confidential notes' end + + context 'as work item' do + let(:noteable) { create(:work_item, :issue, project: project) } + + context 'when using internal param' do + let(:variables_extra) { { internal: true } } + + it_behaves_like 'a Note mutation with confidential notes' + end + + context 'when using deprecated confidential param' do + let(:variables_extra) { { confidential: true } } + + it_behaves_like 'a Note mutation with confidential notes' + end + + context 'without notes widget' do + let(:variables_extra) { {} } + + before do + stub_const('WorkItems::Type::BASE_TYPES', { issue: { name: 'NoNotesWidget', enum_value: 0 } }) + stub_const('WorkItems::Type::WIDGETS_FOR_TYPE', { issue: [::WorkItems::Widgets::Description] }) + end + + it_behaves_like 'a Note mutation that does not create a Note' + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end + end end context 'when body only contains quick actions' do diff --git a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb index 49f09fadfea..eb45e2aa033 100644 --- a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb @@ -2,17 +2,14 @@ require 'spec_helper' -RSpec.describe 'Destroying a Note' do +RSpec.describe 'Destroying a Note', feature_category: :team_planning do include GraphqlHelpers - let!(:note) { create(:note) } - let(:mutation) do - variables = { - id: GitlabSchema.id_from_object(note).to_s - } - - graphql_mutation(:destroy_note, variables) - end + let(:noteable) { create(:work_item, :issue) } + let!(:note) { create(:note, noteable: noteable, project: noteable.project) } + let(:global_note_id) { GitlabSchema.id_from_object(note).to_s } + let(:variables) { { id: global_note_id } } + let(:mutation) { graphql_mutation(:destroy_note, variables) } def mutation_response graphql_mutation_response(:destroy_note) @@ -47,5 +44,31 @@ RSpec.describe 'Destroying a Note' do expect(mutation_response).to have_key('note') expect(mutation_response['note']).to be_nil end + + context 'when note is system' do + let!(:note) { create(:note, :system) } + + it 'does not destroy system note' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { Note.count } + end + end + + context 'without notes widget' do + before do + stub_const('WorkItems::Type::BASE_TYPES', { issue: { name: 'NoNotesWidget', enum_value: 0 } }) + stub_const('WorkItems::Type::WIDGETS_FOR_TYPE', { issue: [::WorkItems::Widgets::Description] }) + end + + it 'does not update the Note' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to not_change { Note.count } + end + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end end end diff --git a/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb index c4674155aa0..e9cd27a1e20 100644 --- a/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Repositioning an ImageDiffNote' do +RSpec.describe 'Repositioning an ImageDiffNote', feature_category: :team_planning do include GraphqlHelpers let_it_be(:noteable) { create(:merge_request) } 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 cfd0b34b815..a5cd3c8b019 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' -RSpec.describe 'Updating an image DiffNote' do +RSpec.describe 'Updating an image DiffNote', feature_category: :team_planning 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 bae5c58abff..dff8a87314b 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' -RSpec.describe 'Updating a Note' do +RSpec.describe 'Updating a Note', feature_category: :team_planning do include GraphqlHelpers let!(:note) { create(:note, note: original_body) } @@ -36,49 +36,32 @@ RSpec.describe 'Updating a Note' do it_behaves_like 'a Note mutation when the given resource id is not for a Note' - it 'updates the Note' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(note.reload.note).to eq(updated_body) - end - - it 'returns the updated Note' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(mutation_response['note']['body']).to eq(updated_body) - end + it_behaves_like 'a Note mutation updates a note successfully' + it_behaves_like 'a Note mutation update with errors' + it_behaves_like 'a Note mutation update only with quick actions' - context 'when there are ActiveRecord validation errors' do - let(:params) { { body: '', confidential: true } } + context 'for work item' do + let(:noteable) { create(:work_item, :issue) } + let(:note) { create(:note, noteable: noteable, project: noteable.project, note: original_body) } - it_behaves_like 'a mutation that returns errors in the response', - errors: ["Note can't be blank", 'Confidential can not be changed for existing notes'] + it_behaves_like 'a Note mutation updates a note successfully' + it_behaves_like 'a Note mutation update with errors' + it_behaves_like 'a Note mutation update only with quick actions' - it 'does not update the Note' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(note.reload.note).to eq(original_body) - expect(note.confidential).to be_falsey - end - - it 'returns the original Note' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(mutation_response['note']['body']).to eq(original_body) - expect(mutation_response['note']['confidential']).to be_falsey - end - end + context 'without notes widget' do + before do + stub_const('WorkItems::Type::BASE_TYPES', { issue: { name: 'NoNotesWidget', enum_value: 0 } }) + stub_const('WorkItems::Type::WIDGETS_FOR_TYPE', { issue: [::WorkItems::Widgets::Description] }) + end - context 'when body only contains quick actions' do - let(:updated_body) { '/close' } + it 'does not update the Note' do + post_graphql_mutation(mutation, current_user: current_user) - it 'returns a nil note and empty errors' do - post_graphql_mutation(mutation, current_user: current_user) + expect(note.reload.note).to eq(original_body) + end - expect(mutation_response).to include( - 'errors' => [], - 'note' => nil - ) + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] end end end diff --git a/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb b/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb index 1fe01af4f1c..d0980a2b43d 100644 --- a/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Destroying multiple packages' do +RSpec.describe 'Destroying multiple packages', feature_category: :package_registry do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb b/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb index 7e00f3ca53a..2540e06be9a 100644 --- a/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Updating the packages cleanup policy' do +RSpec.describe 'Updating the packages cleanup policy', feature_category: :package_registry do include GraphqlHelpers using RSpec::Parameterized::TableSyntax diff --git a/spec/requests/api/graphql/mutations/packages/destroy_file_spec.rb b/spec/requests/api/graphql/mutations/packages/destroy_file_spec.rb index cd25aba9e00..a4b7001fdd5 100644 --- a/spec/requests/api/graphql/mutations/packages/destroy_file_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/destroy_file_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Destroying a package file' do +RSpec.describe 'Destroying a package file', feature_category: :package_registry do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/mutations/packages/destroy_files_spec.rb b/spec/requests/api/graphql/mutations/packages/destroy_files_spec.rb index 002cd634ebd..cdd05d80fc2 100644 --- a/spec/requests/api/graphql/mutations/packages/destroy_files_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/destroy_files_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Destroying multiple package files' do +RSpec.describe 'Destroying multiple package files', feature_category: :package_registry do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/mutations/packages/destroy_spec.rb b/spec/requests/api/graphql/mutations/packages/destroy_spec.rb index 2340a6a36d8..86167e7116f 100644 --- a/spec/requests/api/graphql/mutations/packages/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/destroy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Destroying a package' do +RSpec.describe 'Destroying a package', feature_category: :package_registry do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/mutations/release_asset_links/create_spec.rb b/spec/requests/api/graphql/mutations/release_asset_links/create_spec.rb index c7a4cb1ebce..418a0e47a36 100644 --- a/spec/requests/api/graphql/mutations/release_asset_links/create_spec.rb +++ b/spec/requests/api/graphql/mutations/release_asset_links/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creation of a new release asset link' do +RSpec.describe 'Creation of a new release asset link', feature_category: :release_orchestration do include GraphqlHelpers let_it_be(:project) { create(:project, :private, :repository) } diff --git a/spec/requests/api/graphql/mutations/release_asset_links/delete_spec.rb b/spec/requests/api/graphql/mutations/release_asset_links/delete_spec.rb index 57489c82ec2..b6d2c3f691d 100644 --- a/spec/requests/api/graphql/mutations/release_asset_links/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/release_asset_links/delete_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Deletes a release asset link' do +RSpec.describe 'Deletes a release asset link', feature_category: :release_orchestration do include GraphqlHelpers let_it_be(:project) { create(:project, :private, :repository) } diff --git a/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb b/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb index 92b558d4be3..61395cc4042 100644 --- a/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb +++ b/spec/requests/api/graphql/mutations/release_asset_links/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Updating an existing release asset link' do +RSpec.describe 'Updating an existing release asset link', feature_category: :release_orchestration do include GraphqlHelpers let_it_be(:project) { create(:project, :private, :repository) } diff --git a/spec/requests/api/graphql/mutations/releases/create_spec.rb b/spec/requests/api/graphql/mutations/releases/create_spec.rb index 2541072b766..295b8c0e97e 100644 --- a/spec/requests/api/graphql/mutations/releases/create_spec.rb +++ b/spec/requests/api/graphql/mutations/releases/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Creation of a new release' do +RSpec.describe 'Creation of a new release', feature_category: :release_orchestration do include GraphqlHelpers include Presentable diff --git a/spec/requests/api/graphql/mutations/releases/delete_spec.rb b/spec/requests/api/graphql/mutations/releases/delete_spec.rb index eb4f0b594ea..bb398787cc6 100644 --- a/spec/requests/api/graphql/mutations/releases/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/releases/delete_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Deleting a release' do +RSpec.describe 'Deleting a release', feature_category: :release_orchestration do include GraphqlHelpers include Presentable diff --git a/spec/requests/api/graphql/mutations/releases/update_spec.rb b/spec/requests/api/graphql/mutations/releases/update_spec.rb index 240db764f40..2b88576a70e 100644 --- a/spec/requests/api/graphql/mutations/releases/update_spec.rb +++ b/spec/requests/api/graphql/mutations/releases/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Updating an existing release' do +RSpec.describe 'Updating an existing release', feature_category: :release_orchestration do include GraphqlHelpers include Presentable diff --git a/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb index 0c034f38dc8..cdd25f8f6ec 100644 --- a/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb +++ b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'ConfigureSastIac' do +RSpec.describe 'ConfigureSastIac', feature_category: :static_application_security_testing do include GraphqlHelpers let_it_be(:project) { create(:project, :test_repo) } diff --git a/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb index 8fa6e44b208..370abf2fe00 100644 --- a/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb +++ b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_secret_detection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'ConfigureSecretDetection' do +RSpec.describe 'ConfigureSecretDetection', feature_category: :secret_detection do include GraphqlHelpers let_it_be(:project) { create(:project, :test_repo) } diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb index 264fa5732c3..0b1af2bf628 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' -RSpec.describe 'Creating a Snippet' do +RSpec.describe 'Creating a Snippet', feature_category: :source_code_management do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb index 1be8ce142ac..09e884d9412 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' -RSpec.describe 'Destroying a Snippet' do +RSpec.describe 'Destroying a Snippet', feature_category: :source_code_management 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 77fd6cddc09..9a8c027da8a 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' -RSpec.describe 'Mark snippet as spam' do +RSpec.describe 'Mark snippet as spam', feature_category: :source_code_management do include GraphqlHelpers include AfterNextHelpers diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb index 1a5d3620f22..fa087e6773c 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' -RSpec.describe 'Updating a Snippet' do +RSpec.describe 'Updating a Snippet', feature_category: :source_code_management do include GraphqlHelpers include SessionHelpers @@ -192,11 +192,18 @@ RSpec.describe 'Updating a Snippet' do stub_session('warden.user.user.key' => [[current_user.id], current_user.authenticatable_salt]) end - it_behaves_like 'Snowplow event tracking' do + it_behaves_like 'Snowplow event tracking with RedisHLL context' do + let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } let(:user) { current_user } + let(:property) { 'g_edit_by_snippet_ide' } let(:namespace) { project.namespace } - let(:category) { 'ide_edit' } - let(:action) { 'g_edit_by_snippet_ide' } + let(:category) { 'Gitlab::UsageDataCounters::EditorUniqueCounter' } + let(:action) { 'ide_edit' } + let(:label) { 'usage_activity_by_stage_monthly.create.action_monthly_active_users_ide_edit' } + let(:context) do + [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context] + end + let(:feature_flag_name) { :route_hll_to_snowplow_phase2 } end end diff --git a/spec/requests/api/graphql/mutations/timelogs/create_spec.rb b/spec/requests/api/graphql/mutations/timelogs/create_spec.rb index eea04b89783..42249818a92 100644 --- a/spec/requests/api/graphql/mutations/timelogs/create_spec.rb +++ b/spec/requests/api/graphql/mutations/timelogs/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Create a timelog' do +RSpec.describe 'Create a timelog', feature_category: :team_planning do include GraphqlHelpers let_it_be(:author) { create(:user) } @@ -11,14 +11,6 @@ RSpec.describe 'Create a timelog' do let(:current_user) { nil } let(:users_container) { project } - let(:mutation) do - graphql_mutation(:timelogCreate, { - 'time_spent' => time_spent, - 'spent_at' => '2022-07-08', - 'summary' => 'Test summary', - 'issuable_id' => issuable.to_global_id.to_s - }) - end let(:mutation_response) { graphql_mutation_response(:timelog_create) } diff --git a/spec/requests/api/graphql/mutations/timelogs/delete_spec.rb b/spec/requests/api/graphql/mutations/timelogs/delete_spec.rb index d304bfbdf00..d04b4d193e6 100644 --- a/spec/requests/api/graphql/mutations/timelogs/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/timelogs/delete_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Delete a timelog' do +RSpec.describe 'Delete a timelog', feature_category: :team_planning do include GraphqlHelpers let_it_be(:author) { create(:user) } let_it_be(:project) { create(:project, :public) } diff --git a/spec/requests/api/graphql/mutations/todos/create_spec.rb b/spec/requests/api/graphql/mutations/todos/create_spec.rb index aca00519682..5d7ecb3c927 100644 --- a/spec/requests/api/graphql/mutations/todos/create_spec.rb +++ b/spec/requests/api/graphql/mutations/todos/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Create a todo' do +RSpec.describe 'Create a todo', feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { create(:user) } 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 dc20fde8e3c..c611c6ee2a1 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' -RSpec.describe 'Marking all todos done' do +RSpec.describe 'Marking all todos done', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } 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 7f5ea71c760..60700d8024c 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' -RSpec.describe 'Marking todos done' do +RSpec.describe 'Marking todos done', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb index 4316bd060c1..9daa243cf8e 100644 --- a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb +++ b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Restoring many Todos' do +RSpec.describe 'Restoring many Todos', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/mutations/todos/restore_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_spec.rb index d995191c97e..868298763ec 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' -RSpec.describe 'Restoring Todos' do +RSpec.describe 'Restoring Todos', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/mutations/uploads/delete_spec.rb b/spec/requests/api/graphql/mutations/uploads/delete_spec.rb index 2d1b33cc086..08dbbe23b6b 100644 --- a/spec/requests/api/graphql/mutations/uploads/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/uploads/delete_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Delete an upload' do +RSpec.describe 'Delete an upload', feature_category: :navigation do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb b/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb index 28a46583d2a..eb35d310760 100644 --- a/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb +++ b/spec/requests/api/graphql/mutations/user_callouts/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Create a user callout' do +RSpec.describe 'Create a user callout', feature_category: :navigation do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb index e1c7fd9d60d..31d17401b9e 100644 --- a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb +++ b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::UserPreferences::Update do +RSpec.describe Mutations::UserPreferences::Update, feature_category: :users do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb index c6a980b5cef..97bf060356a 100644 --- a/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Create a work item from a task in a work item's description" do +RSpec.describe "Create a work item from a task in a work item's description", feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/mutations/work_items/create_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_spec.rb index be3917316c3..16f78b67b5c 100644 --- a/spec/requests/api/graphql/mutations/work_items/create_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Create a work item' do +RSpec.describe 'Create a work item', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } @@ -123,7 +123,7 @@ RSpec.describe 'Create a work item' do post_graphql_mutation(mutation, current_user: current_user) expect(mutation_response['errors']) - .to contain_exactly(/cannot be added: only Issue and Incident can be parent of Task./) + .to contain_exactly(/cannot be added: is not allowed to add this type of parent/) expect(mutation_response['workItem']).to be_nil end end diff --git a/spec/requests/api/graphql/mutations/work_items/delete_spec.rb b/spec/requests/api/graphql/mutations/work_items/delete_spec.rb index 0a84225a7ab..e25ff5613e4 100644 --- a/spec/requests/api/graphql/mutations/work_items/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/delete_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Delete a work item' do +RSpec.describe 'Delete a work item', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb index c44939c8d54..b1828de046f 100644 --- a/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Delete a task in a work item's description" do +RSpec.describe "Delete a task in a work item's description", feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb index 96736457f26..14cb18d04b8 100644 --- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Update a work item' do +RSpec.describe 'Update a work item', feature_category: :team_planning do include GraphqlHelpers let_it_be(:group) { create(:group) } @@ -339,7 +339,7 @@ RSpec.describe 'Update a work item' do let_it_be(:invalid_parent) { create(:work_item, :task, project: project) } context 'when parent work item type is invalid' do - let(:error) { "#{work_item.to_reference} cannot be added: only Issue and Incident can be parent of Task." } + let(:error) { "#{work_item.to_reference} cannot be added: is not allowed to add this type of parent" } let(:input) do { 'hierarchyWidget' => { 'parentId' => invalid_parent.to_global_id.to_s }, 'title' => 'new title' } end @@ -450,7 +450,7 @@ RSpec.describe 'Update a work item' do let(:input) { { 'hierarchyWidget' => { 'childrenIds' => children_ids } } } let(:error) do - "#{invalid_child.to_reference} cannot be added: only Task can be assigned as a child in hierarchy." + "#{invalid_child.to_reference} cannot be added: is not allowed to add this type of parent" end context 'when child work item type is invalid' do @@ -632,7 +632,7 @@ RSpec.describe 'Update a work item' do end context 'when unsupported widget input is sent' do - let_it_be(:test_case) { create(:work_item_type, :default, :test_case, name: 'some_test_case_name') } + let_it_be(:test_case) { create(:work_item_type, :default, :test_case) } let_it_be(:work_item) { create(:work_item, work_item_type: test_case, project: project) } let(:input) do @@ -642,7 +642,7 @@ RSpec.describe 'Update a work item' do end it_behaves_like 'a mutation that returns top-level errors', - errors: ["Following widget keys are not supported by some_test_case_name type: [:hierarchy_widget]"] + errors: ["Following widget keys are not supported by Test Case type: [:hierarchy_widget]"] end end end diff --git a/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb index 55285be5a5d..999c685ac6a 100644 --- a/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Update a work item task' do +RSpec.describe 'Update a work item task', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/namespace/package_settings_spec.rb b/spec/requests/api/graphql/namespace/package_settings_spec.rb index 42fd07dbdc7..bd441032626 100644 --- a/spec/requests/api/graphql/namespace/package_settings_spec.rb +++ b/spec/requests/api/graphql/namespace/package_settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting namespace package settings in a namespace' do +RSpec.describe 'getting namespace package settings in a namespace', feature_category: :package_registry do include GraphqlHelpers let_it_be(:package_settings) { create(:namespace_package_setting) } diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb index d5410f1a7cb..4e12da3e3ab 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' -RSpec.describe 'getting projects' do +RSpec.describe 'getting projects', feature_category: :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 8d8a0baae36..cee698d6dc5 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' -RSpec.describe 'rendering namespace statistics' do +RSpec.describe 'rendering namespace statistics', feature_category: :metrics do include GraphqlHelpers let(:namespace) { user.namespace } diff --git a/spec/requests/api/graphql/namespace_query_spec.rb b/spec/requests/api/graphql/namespace_query_spec.rb index e17469901c6..d12a3875ebf 100644 --- a/spec/requests/api/graphql/namespace_query_spec.rb +++ b/spec/requests/api/graphql/namespace_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query' do +RSpec.describe 'Query', feature_category: :subgroups do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/packages/composer_spec.rb b/spec/requests/api/graphql/packages/composer_spec.rb index 89c01d44771..dd61582b055 100644 --- a/spec/requests/api/graphql/packages/composer_spec.rb +++ b/spec/requests/api/graphql/packages/composer_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'package details' do +RSpec.describe 'package details', feature_category: :package_registry do include GraphqlHelpers include_context 'package details setup' diff --git a/spec/requests/api/graphql/packages/conan_spec.rb b/spec/requests/api/graphql/packages/conan_spec.rb index 7ad85edecef..b8226c482ac 100644 --- a/spec/requests/api/graphql/packages/conan_spec.rb +++ b/spec/requests/api/graphql/packages/conan_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'conan package details' do +RSpec.describe 'conan package details', feature_category: :package_registry do include GraphqlHelpers include_context 'package details setup' diff --git a/spec/requests/api/graphql/packages/helm_spec.rb b/spec/requests/api/graphql/packages/helm_spec.rb index 79a589e2dc2..65b3f80d6df 100644 --- a/spec/requests/api/graphql/packages/helm_spec.rb +++ b/spec/requests/api/graphql/packages/helm_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'helm package details' do +RSpec.describe 'helm package details', feature_category: :package_registry do include GraphqlHelpers include_context 'package details setup' diff --git a/spec/requests/api/graphql/packages/maven_spec.rb b/spec/requests/api/graphql/packages/maven_spec.rb index b7f39efcf73..26c45ada4a1 100644 --- a/spec/requests/api/graphql/packages/maven_spec.rb +++ b/spec/requests/api/graphql/packages/maven_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'maven package details' do +RSpec.describe 'maven package details', feature_category: :package_registry do include GraphqlHelpers include_context 'package details setup' diff --git a/spec/requests/api/graphql/packages/nuget_spec.rb b/spec/requests/api/graphql/packages/nuget_spec.rb index 7de132d1574..1c3af46909e 100644 --- a/spec/requests/api/graphql/packages/nuget_spec.rb +++ b/spec/requests/api/graphql/packages/nuget_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'nuget package details' do +RSpec.describe 'nuget package details', feature_category: :package_registry do include GraphqlHelpers include_context 'package details setup' diff --git a/spec/requests/api/graphql/packages/package_spec.rb b/spec/requests/api/graphql/packages/package_spec.rb index 02a3206f587..42927634119 100644 --- a/spec/requests/api/graphql/packages/package_spec.rb +++ b/spec/requests/api/graphql/packages/package_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'package details' do +RSpec.describe 'package details', feature_category: :package_registry do include GraphqlHelpers let_it_be_with_reload(:group) { create(:group) } @@ -226,5 +226,16 @@ RSpec.describe 'package details' do end end end + + context 'with package that has no default status' do + before do + composer_package.update!(status: :error) + subject + end + + it "does not return package's details" do + expect(package_details).to be_nil + end + end end end diff --git a/spec/requests/api/graphql/packages/pypi_spec.rb b/spec/requests/api/graphql/packages/pypi_spec.rb index c0e589f3597..4441f04dabb 100644 --- a/spec/requests/api/graphql/packages/pypi_spec.rb +++ b/spec/requests/api/graphql/packages/pypi_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'pypi package details' do +RSpec.describe 'pypi package details', feature_category: :package_registry do include GraphqlHelpers include_context 'package details setup' 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 a59402208ec..c4843c3cf97 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' -RSpec.describe 'getting Alert Management Alert Assignees' do +RSpec.describe 'getting Alert Management Alert Assignees', feature_category: :projects do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/project/alert_management/alert/issue_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/issue_spec.rb index 29896c16f5b..3c9ec4fb60b 100644 --- a/spec/requests/api/graphql/project/alert_management/alert/issue_spec.rb +++ b/spec/requests/api/graphql/project/alert_management/alert/issue_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting Alert Management Alert Issue' do +RSpec.describe 'getting Alert Management Alert Issue', feature_category: :team_planning 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 index 352a94cfc1d..b430fdeb18f 100644 --- 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 @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting Alert Management Alert Assignees' do +RSpec.describe 'getting Alert Management Alert Assignees', feature_category: :projects do include GraphqlHelpers let_it_be(:project) { create(:project) } 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 72d185144ef..16dd0dfcfcb 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,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting Alert Management Alert Notes' do +RSpec.describe 'getting Alert Management Alert Notes', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/project/alert_management/alert/todos_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/todos_spec.rb index ca58079fdfe..ad4361dfa50 100644 --- a/spec/requests/api/graphql/project/alert_management/alert/todos_spec.rb +++ b/spec/requests/api/graphql/project/alert_management/alert/todos_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting Alert Management Alert Assignees' do +RSpec.describe 'getting Alert Management Alert Assignees', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project) } 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 ecd93d169d3..7ce5bf23357 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' -RSpec.describe 'getting Alert Management Alert counts by status' do +RSpec.describe 'getting Alert Management Alert counts by status', feature_category: :incident_management 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 fe77d9dc86d..304edfbf4e4 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' -RSpec.describe 'getting Alert Management Alerts' do +RSpec.describe 'getting Alert Management Alerts', feature_category: :incident_management do include GraphqlHelpers let_it_be(:payload) { { 'custom' => { 'alert' => 'payload' }, 'runbook' => 'runbook' } } @@ -59,6 +59,7 @@ RSpec.describe 'getting Alert Management Alerts' do it 'returns the correct properties of the alerts' do expect(first_alert).to include( + 'id' => triggered_alert.to_global_id.to_s, 'iid' => triggered_alert.iid.to_s, 'title' => triggered_alert.title, 'description' => triggered_alert.description, @@ -80,6 +81,7 @@ RSpec.describe 'getting Alert Management Alerts' do ) expect(second_alert).to include( + 'id' => resolved_alert.to_global_id.to_s, 'iid' => resolved_alert.iid.to_s, 'status' => 'RESOLVED', 'endedAt' => resolved_alert.ended_at.strftime('%Y-%m-%dT%H:%M:%SZ') diff --git a/spec/requests/api/graphql/project/alert_management/integrations_spec.rb b/spec/requests/api/graphql/project/alert_management/integrations_spec.rb index 773922c1864..e8d19513a4e 100644 --- a/spec/requests/api/graphql/project/alert_management/integrations_spec.rb +++ b/spec/requests/api/graphql/project/alert_management/integrations_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting Alert Management Integrations' do +RSpec.describe 'getting Alert Management Integrations', feature_category: :integrations do include ::Gitlab::Routing include GraphqlHelpers diff --git a/spec/requests/api/graphql/project/base_service_spec.rb b/spec/requests/api/graphql/project/base_service_spec.rb index 58d10ade8cf..7b1b95eaf58 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' -RSpec.describe 'query Jira service' do +RSpec.describe 'query Jira service', feature_category: :authentication_and_authorization do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb b/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb index a80f683ea93..d7672cc5116 100644 --- a/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb +++ b/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb @@ -2,6 +2,6 @@ require 'spec_helper' -RSpec.describe 'getting merge access levels for a branch protection' do - include_examples 'perform graphql requests for AccessLevel type objects', :merge +RSpec.describe 'getting merge access levels for a branch protection', feature_category: :source_code_management do + it_behaves_like 'a GraphQL query for access levels', :merge end diff --git a/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb b/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb index cfdaf1096c3..65b9bc93a6b 100644 --- a/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb +++ b/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb @@ -2,6 +2,6 @@ require 'spec_helper' -RSpec.describe 'getting push access levels for a branch protection' do - include_examples 'perform graphql requests for AccessLevel type objects', :push +RSpec.describe 'getting push access levels for a branch protection', feature_category: :source_code_management do + it_behaves_like 'a GraphQL query for access levels', :push end diff --git a/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb b/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb index 8a3f546ef95..560819cb989 100644 --- a/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb +++ b/spec/requests/api/graphql/project/branch_rules/branch_protection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting branch protection for a branch rule' do +RSpec.describe 'getting branch protection for a branch rule', feature_category: :source_code_management do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/project/branch_rules_spec.rb b/spec/requests/api/graphql/project/branch_rules_spec.rb index ed866305445..7f6a66e2377 100644 --- a/spec/requests/api/graphql/project/branch_rules_spec.rb +++ b/spec/requests/api/graphql/project/branch_rules_spec.rb @@ -2,21 +2,11 @@ require 'spec_helper' -RSpec.describe 'getting list of branch rules for a project' do +RSpec.describe 'getting list of branch rules for a project', feature_category: :source_code_management do include GraphqlHelpers let_it_be(:project) { create(:project, :repository, :public) } let_it_be(:current_user) { create(:user) } - let_it_be(:branch_name_a) { TestEnv::BRANCH_SHA.each_key.first } - let_it_be(:branch_name_b) { 'diff-*' } - let_it_be(:branch_rules) { [branch_rule_a, branch_rule_b] } - let_it_be(:branch_rule_a) do - create(:protected_branch, project: project, name: branch_name_a) - end - - let_it_be(:branch_rule_b) do - create(:protected_branch, project: project, name: branch_name_b) - end let(:branch_rules_data) { graphql_data_at('project', 'branchRules', 'edges') } let(:variables) { { path: project.full_path } } @@ -61,39 +51,39 @@ RSpec.describe 'getting list of branch rules for a project' do end describe 'queries' do + include_context 'when user tracking is disabled' + + let(:query) do + <<~GQL + query($path: ID!) { + project(fullPath: $path) { + branchRules { + nodes { + matchingBranchesCount + } + } + } + } + GQL + end + before do - # rubocop:disable RSpec/AnyInstanceOf - allow_any_instance_of(User).to receive(:update_tracked_fields!) - allow_any_instance_of(Users::ActivityService).to receive(:execute) - # rubocop:enable RSpec/AnyInstanceOf + create(:protected_branch, project: project) allow_next_instance_of(Resolvers::ProjectResolver) do |resolver| allow(resolver).to receive(:resolve) .with(full_path: project.full_path) .and_return(project) end allow(project.repository).to receive(:branch_names).and_call_original - allow(project.repository.gitaly_ref_client).to receive(:branch_names).and_call_original end - it 'matching_branches_count avoids N+1 queries' do - query = <<~GQL - query($path: ID!) { - project(fullPath: $path) { - branchRules { - nodes { - matchingBranchesCount - } - } - } - } - GQL - - control = ActiveRecord::QueryRecorder.new do + it 'avoids N+1 queries', :use_sql_query_cache, :aggregate_failures do + control = ActiveRecord::QueryRecorder.new(skip_cached: false) do post_graphql(query, current_user: current_user, variables: variables) end # Verify the response includes the field - expect_n_matching_branches_count_fields(2) + expect_n_matching_branches_count_fields(1) create(:protected_branch, project: project) create(:protected_branch, name: '*', project: project) @@ -102,10 +92,8 @@ RSpec.describe 'getting list of branch rules for a project' do post_graphql(query, current_user: current_user, variables: variables) end.not_to exceed_all_query_limit(control) + expect_n_matching_branches_count_fields(3) expect(project.repository).to have_received(:branch_names).at_least(2).times - expect(project.repository.gitaly_ref_client).to have_received(:branch_names).once - - expect_n_matching_branches_count_fields(4) end def expect_n_matching_branches_count_fields(count) @@ -118,21 +106,28 @@ RSpec.describe 'getting list of branch rules for a project' do end describe 'response' do + let_it_be(:branch_name_a) { TestEnv::BRANCH_SHA.each_key.first } + let_it_be(:branch_name_b) { 'diff-*' } + let_it_be(:branch_rules) { [branch_rule_a, branch_rule_b] } + let_it_be(:branch_rule_a) do + create(:protected_branch, project: project, name: branch_name_a, id: 9999) + end + + let_it_be(:branch_rule_b) do + create(:protected_branch, project: project, name: branch_name_b, id: 10000) + end + + # branchRules are returned in reverse order, newest first, sorted by primary_key. + let(:branch_rule_b_data) { branch_rules_data.dig(0, 'node') } + let(:branch_rule_a_data) { branch_rules_data.dig(1, 'node') } + before do post_graphql(query, current_user: current_user, variables: variables) end it_behaves_like 'a working graphql query' - it 'includes all fields', :aggregate_failures do - # Responses will be sorted alphabetically. Branch names for this spec - # come from an external constant so we check which is first - br_a_idx = branch_name_a < branch_name_b ? 0 : 1 - br_b_idx = 1 - br_a_idx - - branch_rule_a_data = branch_rules_data.dig(br_a_idx, 'node') - branch_rule_b_data = branch_rules_data.dig(br_b_idx, 'node') - + it 'includes all fields', :use_sql_query_cache, :aggregate_failures do expect(branch_rule_a_data['name']).to eq(branch_name_a) expect(branch_rule_a_data['isDefault']).to be(true).or be(false) expect(branch_rule_a_data['branchProtection']).to be_present diff --git a/spec/requests/api/graphql/project/cluster_agents_spec.rb b/spec/requests/api/graphql/project/cluster_agents_spec.rb index bb716cf2849..0881eb9cdc3 100644 --- a/spec/requests/api/graphql/project/cluster_agents_spec.rb +++ b/spec/requests/api/graphql/project/cluster_agents_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project.cluster_agents' do +RSpec.describe 'Project.cluster_agents', feature_category: :kubernetes_management do include GraphqlHelpers let_it_be(:project) { create(:project, :public) } 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 e3ea9e46353..e484e0618aa 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' -RSpec.describe 'getting a repository in a project' do +RSpec.describe 'getting a repository in a project', feature_category: :container_registry do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb index 01b117a89d8..7ccf8a6f5bf 100644 --- a/spec/requests/api/graphql/project/container_repositories_spec.rb +++ b/spec/requests/api/graphql/project/container_repositories_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting container repositories in a project' do +RSpec.describe 'getting container repositories in a project', feature_category: :container_registry do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/project/deployment_spec.rb b/spec/requests/api/graphql/project/deployment_spec.rb index e5ef7bcafbf..e3ec33ec131 100644 --- a/spec/requests/api/graphql/project/deployment_spec.rb +++ b/spec/requests/api/graphql/project/deployment_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project Deployment query' do +RSpec.describe 'Project Deployment query', feature_category: :continuous_delivery do let_it_be(:project) { create(:project, :private, :repository) } let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } diff --git a/spec/requests/api/graphql/project/environments_spec.rb b/spec/requests/api/graphql/project/environments_spec.rb index e5b6aebbf2c..618f591affa 100644 --- a/spec/requests/api/graphql/project/environments_spec.rb +++ b/spec/requests/api/graphql/project/environments_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project Environments query' do +RSpec.describe 'Project Environments query', feature_category: :continuous_delivery do include GraphqlHelpers let_it_be(:project) { create(:project, :private, :repository) } @@ -47,6 +47,60 @@ RSpec.describe 'Project Environments query' do expect(environment_data['environmentType']).to eq(production.environment_type) end + describe 'user permissions' do + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + environment(name: "#{production.name}") { + userPermissions { + updateEnvironment + destroyEnvironment + stopEnvironment + } + } + } + } + ) + end + + it 'returns user permissions of the environment', :aggregate_failures do + subject + + permission_data = graphql_data.dig('project', 'environment', 'userPermissions') + expect(permission_data['updateEnvironment']).to eq(true) + expect(permission_data['destroyEnvironment']).to eq(false) + expect(permission_data['stopEnvironment']).to eq(true) + end + + context 'when fetching user permissions for multiple environments' do + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + environments { + nodes { + userPermissions { + updateEnvironment + destroyEnvironment + stopEnvironment + } + } + } + } + } + ) + end + + it 'limits the result', :aggregate_failures do + subject + + expect_graphql_errors_to_include('"userPermissions" field can be requested only ' \ + 'for 1 Environment(s) at a time.') + end + end + end + describe 'last deployments of environments' do ::Deployment.statuses.each do |status, _| let_it_be(:"production_#{status}_deployment") do @@ -130,4 +184,81 @@ RSpec.describe 'Project Environments query' do expect(multi).not_to exceed_query_limit(baseline) end end + + describe 'nested environments' do + let_it_be(:testing1) { create(:environment, name: 'testing/one', project: project) } + let_it_be(:testing2) { create(:environment, name: 'testing/two', project: project) } + + context 'with query' do + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + nestedEnvironments { + nodes { + name + size + environment { + name + path + } + } + } + } + } + ) + end + + it 'can fetch nested environments' do + subject + + nested_envs = graphql_data.dig('project', 'nestedEnvironments', 'nodes') + expect(nested_envs.count).to be(3) + expect(nested_envs.pluck('name')).to match_array(%w[production staging testing]) + expect(nested_envs.pluck('size')).to match_array([1, 1, 2]) + expect(nested_envs[0].dig('environment', 'name')).to eq(production.name) + end + + context 'when user is guest' do + let(:user) { create(:user).tap { |u| project.add_guest(u) } } + + it 'returns nothing' do + subject + + nested_envs = graphql_data.dig('project', 'nestedEnvironments', 'nodes') + + expect(nested_envs).to be_nil + end + end + end + + context 'when using pagination' do + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + nestedEnvironments(first: 1) { + nodes { + name + } + pageInfo { + hasPreviousPage + startCursor + endCursor + hasNextPage + } + } + } + } + ) + end + + it 'supports pagination' do + subject + nested_envs = graphql_data.dig('project', 'nestedEnvironments') + expect(nested_envs['nodes'].count).to eq(1) + expect(nested_envs.dig('pageInfo', 'hasNextPage')).to be_truthy + end + end + end end 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 2fe5fb593fe..e1a8304dce6 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' -RSpec.describe 'getting a detailed sentry error' do +RSpec.describe 'getting a detailed sentry error', feature_category: :error_tracking 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 3ca0e35882a..2abb1f62ea9 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' -RSpec.describe 'sentry errors requests' do +RSpec.describe 'sentry errors requests', feature_category: :error_tracking do include GraphqlHelpers let_it_be(:project) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/project/fork_details_spec.rb b/spec/requests/api/graphql/project/fork_details_spec.rb new file mode 100644 index 00000000000..efd48b00833 --- /dev/null +++ b/spec/requests/api/graphql/project/fork_details_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting project fork details', feature_category: :source_code_management do + include GraphqlHelpers + include ProjectForksHelper + + let_it_be(:project) { create(:project, :public, :repository_private, :repository) } + let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } + let_it_be(:forked_project) { fork_project(project, current_user, repository: true) } + + let(:queried_project) { forked_project } + + let(:query) do + graphql_query_for(:project, + { full_path: queried_project.full_path }, <<~QUERY + forkDetails(ref: "feature"){ + ahead + behind + } + QUERY + ) + end + + it 'returns fork details' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['forkDetails']).to eq( + { 'ahead' => 1, 'behind' => 29 } + ) + end + + context 'when a project is not a fork' do + let(:queried_project) { project } + + it 'does not return fork details' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['forkDetails']).to be_nil + end + end + + context 'when a user cannot read the code' do + let_it_be(:current_user) { create(:user) } + + before do + forked_project.update!({ + repository_access_level: 'private', + merge_requests_access_level: 'private' + }) + end + + it 'does not return fork details' do + post_graphql(query, current_user: current_user) + + expect(graphql_data['project']['forkDetails']).to be_nil + end + end +end diff --git a/spec/requests/api/graphql/project/fork_targets_spec.rb b/spec/requests/api/graphql/project/fork_targets_spec.rb index b21a11ff4dc..f2a3901b2c6 100644 --- a/spec/requests/api/graphql/project/fork_targets_spec.rb +++ b/spec/requests/api/graphql/project/fork_targets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting a list of fork targets for a project' do +RSpec.describe 'getting a list of fork targets for a project', feature_category: :source_code_management do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/project/grafana_integration_spec.rb b/spec/requests/api/graphql/project/grafana_integration_spec.rb index e7534945e7a..1d4f52a8ace 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' -RSpec.describe 'Getting Grafana Integration' do +RSpec.describe 'Getting Grafana Integration', feature_category: :metrics do include GraphqlHelpers let_it_be(:project) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb b/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb index 544d2d7bd95..7587b227d9f 100644 --- a/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb +++ b/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting incident timeline events' do +RSpec.describe 'getting incident timeline events', feature_category: :incident_management do include GraphqlHelpers let_it_be(:project) { create(:project) } 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 0444ce43c22..5ccf5c1999a 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,8 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)' do +RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)', +feature_category: :design_management do include GraphqlHelpers include DesignManagementTestHelpers @@ -67,7 +68,7 @@ RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha) query_graphql_field(:design_at_version, dav_params, 'id filename') end - shared_examples :finds_dav do + shared_examples 'finds dav' do it 'finds all the designs as of the given version' do post_query @@ -88,19 +89,19 @@ RSpec.describe 'Query.project(fullPath).issue(iid).designCollection.version(sha) context 'by ID' do let(:dav_params) { { id: global_id_of(design_at_version) } } - include_examples :finds_dav + include_examples 'finds dav' end context 'by filename' do let(:dav_params) { { filename: design.filename } } - include_examples :finds_dav + include_examples 'finds dav' end context 'by design_id' do let(:dav_params) { { design_id: global_id_of(design) } } - include_examples :finds_dav + include_examples 'finds dav' end end 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 46fd65db1c5..a15e4c1e792 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' -RSpec.describe 'Getting versions related to an issue' do +RSpec.describe 'Getting versions related to an issue', feature_category: :design_management 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 965534654ea..3765899daf2 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' -RSpec.describe 'Getting designs related to an issue' do +RSpec.describe 'Getting designs related to an issue', feature_category: :design_management 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 3b1eb0b4b02..69ca7030292 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' -RSpec.describe 'Getting designs related to an issue' do +RSpec.describe 'Getting designs related to an issue', feature_category: :design_management 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 97f5261ef1d..0c7f042510e 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' -RSpec.describe 'getting notes for an issue' do +RSpec.describe 'getting notes for an issue', feature_category: :team_planning 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 2415e9ef60f..bc90f9e89e6 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' -RSpec.describe 'Query.project(fullPath).issue(iid)' do +RSpec.describe 'Query.project(fullPath).issue(iid)', feature_category: :team_planning 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 214165cb171..ec5e3c6f0de 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -2,44 +2,92 @@ require 'spec_helper' -RSpec.describe 'getting an issue list for a project' do +RSpec.describe 'getting an issue list for a project', feature_category: :team_planning do include GraphqlHelpers let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :repository, :public, group: group) } let_it_be(:current_user) { create(:user) } let_it_be(:another_user) { create(:user).tap { |u| group.add_reporter(u) } } - let_it_be(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) } - let_it_be(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) } + let_it_be(:milestone1) { create(:milestone, project: project, due_date: 10.days.from_now) } + let_it_be(:milestone2) { create(:milestone, project: project, due_date: 20.days.from_now) } + let_it_be(:milestone3) { create(:milestone, project: project, due_date: 30.days.from_now) } + let_it_be(:milestone4) { create(:milestone, project: project, due_date: 40.days.from_now) } let_it_be(:priority1) { create(:label, project: project, priority: 1) } let_it_be(:priority2) { create(:label, project: project, priority: 5) } let_it_be(:priority3) { create(:label, project: project, priority: 10) } - let_it_be(:issue_a, reload: true) { create(:issue, project: project, discussion_locked: true, labels: [priority3]) } - let_it_be(:issue_b, reload: true) { create(:issue, :with_alert, project: project, title: 'title matching issue i') } - let_it_be(:issue_c) { create(:issue, project: project, labels: [priority1], milestone: late_milestone) } - let_it_be(:issue_d) { create(:issue, project: project, labels: [priority2]) } - let_it_be(:issue_e) { create(:issue, project: project, milestone: early_milestone) } - let_it_be(:issues, reload: true) { [issue_a, issue_b, issue_c, issue_d, issue_e] } + let_it_be(:issue_a) do + create( + :issue, + project: project, + discussion_locked: true, + labels: [priority3], + relative_position: 1000, + milestone: milestone4 + ) + end - let(:issue_a_gid) { issue_a.to_global_id.to_s } - let(:issue_b_gid) { issue_b.to_global_id.to_s } - let(:issues_data) { graphql_data['project']['issues']['nodes'] } - let(:issue_filter_params) { {} } + let_it_be(:issue_b) do + create( + :issue, + :with_alert, + project: project, + title: 'title matching issue i', + due_date: 3.days.ago, + relative_position: 3000, + labels: [priority2, priority3], + milestone: milestone1 + ) + end - let(:fields) do - <<~QUERY - nodes { - #{all_graphql_fields_for('issues'.classify)} - } - QUERY + let_it_be(:issue_c) do + create( + :issue, + project: project, + labels: [priority1], + milestone: milestone2, + due_date: 1.day.ago, + relative_position: nil + ) + end + + let_it_be(:issue_d) do + create(:issue, + project: project, + labels: [priority2], + due_date: 3.days.from_now, + relative_position: 5000, + milestone: milestone3 + ) + end + + let_it_be(:issue_e) do + create( + :issue, + :confidential, + project: project, + due_date: 1.day.from_now, + relative_position: nil + ) end + let_it_be(:issues, reload: true) { [issue_a, issue_b, issue_c, issue_d, issue_e] } + + let(:issue_nodes_path) { %w[project issues nodes] } + let(:issue_filter_params) { {} } + # All new specs should be added to the shared example if the change also # affects the `issues` query at the root level of the API. # Shared example also used in spec/requests/api/graphql/issues_spec.rb it_behaves_like 'graphql issue list request spec' do - subject(:post_query) { post_graphql(query, current_user: current_user) } + let_it_be(:external_user) { create(:user) } + + let(:public_projects) { [project] } + + before_all do + group.add_developer(current_user) + end # filters let(:expected_negated_assignee_issues) { [issue_b, issue_c, issue_d, issue_e] } @@ -50,24 +98,31 @@ RSpec.describe 'getting an issue list for a project' do let(:unlocked_discussion_issues) { [issue_b, issue_c, issue_d, issue_e] } let(:search_title_term) { 'matching issue' } let(:title_search_issue) { issue_b } + let(:confidential_issues) { [issue_e] } + let(:non_confidential_issues) { [issue_a, issue_b, issue_c, issue_d] } + let(:public_non_confidential_issues) { non_confidential_issues } # sorting let(:data_path) { [:project, :issues] } - let(:expected_severity_sorted_asc) { [issue_c, issue_a, issue_b, issue_e, issue_d] } - let(:expected_priority_sorted_asc) { [issue_e, issue_c, issue_d, issue_a, issue_b] } - let(:expected_priority_sorted_desc) { [issue_c, issue_e, issue_a, issue_d, issue_b] } + let(:expected_priority_sorted_asc) { [issue_b, issue_c, issue_d, issue_a, issue_e] } + let(:expected_priority_sorted_desc) { [issue_a, issue_d, issue_c, issue_b, issue_e] } + let(:expected_due_date_sorted_desc) { [issue_d, issue_e, issue_c, issue_b, issue_a] } + let(:expected_due_date_sorted_asc) { [issue_b, issue_c, issue_e, issue_d, issue_a] } + let(:expected_relative_position_sorted_asc) { [issue_a, issue_b, issue_d, issue_c, issue_e] } + let(:expected_label_priority_sorted_asc) { [issue_c, issue_d, issue_b, issue_a, issue_e] } + let(:expected_label_priority_sorted_desc) { [issue_a, issue_d, issue_b, issue_c, issue_e] } + let(:expected_milestone_sorted_asc) { [issue_b, issue_c, issue_d, issue_a, issue_e] } + let(:expected_milestone_sorted_desc) { [issue_a, issue_d, issue_c, issue_b, issue_e] } + + # N+1 queries + let(:same_project_issue1) { issue_a } + let(:same_project_issue2) { issue_b } before_all do issue_a.assignee_ids = current_user.id issue_b.assignee_ids = another_user.id create(:award_emoji, :upvote, user: current_user, awardable: issue_a) - - # severity sorting - create(:issuable_severity, issue: issue_a, severity: :unknown) - create(:issuable_severity, issue: issue_b, severity: :low) - create(:issuable_severity, issue: issue_d, severity: :critical) - create(:issuable_severity, issue: issue_e, severity: :high) end def pagination_query(params) @@ -77,591 +132,10 @@ RSpec.describe 'getting an issue list for a project' do query_graphql_field(:issues, params, "#{page_info} nodes { id }") ) end - end - - context 'when limiting the number of results' do - let(:query) do - <<~GQL - query($path: ID!, $n: Int) { - project(fullPath: $path) { - issues(first: $n) { #{fields} } - } - } - GQL - end - - let(:issue_limit) { 1 } - let(:variables) do - { path: project.full_path, n: issue_limit } - end - - it_behaves_like 'a working graphql query' do - before do - post_graphql(query, current_user: current_user, variables: variables) - end - - it 'only returns N issues' do - expect(issues_data.size).to eq(issue_limit) - end - end - - context 'when no limit is provided' do - let(:issue_limit) { nil } - - it 'returns all issues' do - post_graphql(query, current_user: current_user, variables: variables) - - expect(issues_data.size).to be > 1 - end - end - - it 'is expected to check permissions on the first issue only' do - allow(Ability).to receive(:allowed?).and_call_original - # Newest first, we only want to see the newest checked - expect(Ability).not_to receive(:allowed?).with(current_user, :read_issue, issues.first) - - post_graphql(query, current_user: current_user, variables: variables) - end - end - - context 'when the user does not have access to the issue' do - it 'returns nil' do - project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) - - post_graphql(query) - - expect(issues_data).to eq([]) - end - end - - context 'when there is a confidential issue' do - let_it_be(:confidential_issue) do - create(:issue, :confidential, project: project) - end - - let(:confidential_issue_gid) { confidential_issue.to_global_id.to_s } - - context 'when the user cannot see confidential issues' do - it 'returns issues without confidential issues' do - post_graphql(query, current_user: current_user) - - expect(issues_data.size).to eq(5) - - issues_data.each do |issue| - expect(issue['confidential']).to eq(false) - end - end - - context 'filtering for confidential issues' do - let(:issue_filter_params) { { confidential: true } } - - it 'returns no issues' do - post_graphql(query, current_user: current_user) - - expect(issues_data.size).to eq(0) - end - end - - context 'filtering for non-confidential issues' do - let(:issue_filter_params) { { confidential: false } } - - it 'returns correctly filtered issues' do - post_graphql(query, current_user: current_user) - - expect(issue_ids).to match_array(issues.map { |i| i.to_gid.to_s }) - end - end - end - - context 'when the user can see confidential issues' do - before do - project.add_developer(current_user) - end - - it 'returns issues with confidential issues' do - post_graphql(query, current_user: current_user) - - expect(issues_data.size).to eq(6) - - confidentials = issues_data.map do |issue| - issue['confidential'] - end - - expect(confidentials).to contain_exactly(true, false, false, false, false, false) - end - - context 'filtering for confidential issues' do - let(:issue_filter_params) { { confidential: true } } - - it 'returns correctly filtered issues' do - post_graphql(query, current_user: current_user) - - expect(issue_ids).to contain_exactly(confidential_issue_gid) - end - end - - context 'filtering for non-confidential issues' do - let(:issue_filter_params) { { confidential: false } } - - it 'returns correctly filtered issues' do - post_graphql(query, current_user: current_user) - - expect(issue_ids).to match_array([issue_a, issue_b, issue_c, issue_d, issue_e].map { |i| i.to_gid.to_s }) - end - end - end - end - - describe 'sorting and pagination' do - let_it_be(:sort_project) { create(:project, :public) } - let_it_be(:data_path) { [:project, :issues] } - - def pagination_query(params) - graphql_query_for( - :project, - { full_path: sort_project.full_path }, - query_graphql_field(:issues, params, "#{page_info} nodes { iid }") - ) - end - def pagination_results_data(data) - data.map { |issue| issue['iid'].to_i } + def post_query(request_user = current_user) + post_graphql(query, current_user: request_user) end - - # rubocop:disable RSpec/MultipleMemoizedHelpers - context 'when sorting by due date' do - let_it_be(:due_issue1) { create(:issue, project: sort_project, due_date: 3.days.from_now) } - let_it_be(:due_issue2) { create(:issue, project: sort_project, due_date: nil) } - let_it_be(:due_issue3) { create(:issue, project: sort_project, due_date: 2.days.ago) } - let_it_be(:due_issue4) { create(:issue, project: sort_project, due_date: nil) } - let_it_be(:due_issue5) { create(:issue, project: sort_project, due_date: 1.day.ago) } - - context 'when ascending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :DUE_DATE_ASC } - let(:first_param) { 2 } - let(:all_records) { [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] } - end - end - - context 'when descending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :DUE_DATE_DESC } - let(:first_param) { 2 } - let(:all_records) { [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] } - end - end - end - - context 'when sorting by relative position' do - let_it_be(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) } - let_it_be(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) } - let_it_be(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) } - let_it_be(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) } - let_it_be(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) } - - context 'when ascending' do - it_behaves_like 'sorted paginated query', is_reversible: true do - let(:sort_param) { :RELATIVE_POSITION_ASC } - let(:first_param) { 2 } - let(:all_records) do - [ - relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, - relative_issue2.iid, relative_issue4.iid - ] - end - end - end - end - - context 'when sorting by label priority' do - let_it_be(:label1) { create(:label, project: sort_project, priority: 1) } - let_it_be(:label2) { create(:label, project: sort_project, priority: 5) } - let_it_be(:label3) { create(:label, project: sort_project, priority: 10) } - let_it_be(:label_issue1) { create(:issue, project: sort_project, labels: [label1]) } - let_it_be(:label_issue2) { create(:issue, project: sort_project, labels: [label2]) } - let_it_be(:label_issue3) { create(:issue, project: sort_project, labels: [label1, label3]) } - let_it_be(:label_issue4) { create(:issue, project: sort_project) } - - context 'when ascending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :LABEL_PRIORITY_ASC } - let(:first_param) { 2 } - let(:all_records) { [label_issue3.iid, label_issue1.iid, label_issue2.iid, label_issue4.iid] } - end - end - - context 'when descending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :LABEL_PRIORITY_DESC } - let(:first_param) { 2 } - let(:all_records) { [label_issue2.iid, label_issue3.iid, label_issue1.iid, label_issue4.iid] } - end - end - end - # rubocop:enable RSpec/MultipleMemoizedHelpers - - context 'when sorting by milestone due date' do - let_it_be(:early_milestone) { create(:milestone, project: sort_project, due_date: 10.days.from_now) } - let_it_be(:late_milestone) { create(:milestone, project: sort_project, due_date: 30.days.from_now) } - let_it_be(:milestone_issue1) { create(:issue, project: sort_project) } - let_it_be(:milestone_issue2) { create(:issue, project: sort_project, milestone: early_milestone) } - let_it_be(:milestone_issue3) { create(:issue, project: sort_project, milestone: late_milestone) } - - context 'when ascending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :MILESTONE_DUE_ASC } - let(:first_param) { 2 } - let(:all_records) { [milestone_issue2.iid, milestone_issue3.iid, milestone_issue1.iid] } - end - end - - context 'when descending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :MILESTONE_DUE_DESC } - let(:first_param) { 2 } - let(:all_records) { [milestone_issue3.iid, milestone_issue2.iid, milestone_issue1.iid] } - end - end - end - end - - context 'when fetching alert management alert' do - let(:fields) do - <<~QUERY - nodes { - iid - alertManagementAlert { - title - } - alertManagementAlerts { - nodes { - title - } - } - } - QUERY - end - - # Alerts need to have developer permission and above - before do - project.add_developer(current_user) - end - - it 'avoids N+1 queries' do - control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) } - - create(:alert_management_alert, :with_incident, project: project) - - expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control) - end - - it 'returns the alert data' do - post_graphql(query, current_user: current_user) - - alert_titles = issues_data.map { |issue| issue.dig('alertManagementAlert', 'title') } - expected_titles = issues.map { |issue| issue.alert_management_alert&.title } - - expect(alert_titles).to contain_exactly(*expected_titles) - end - - it 'returns the alerts data' do - post_graphql(query, current_user: current_user) - - alert_titles = issues_data.map { |issue| issue.dig('alertManagementAlerts', 'nodes') } - expected_titles = issues.map do |issue| - issue.alert_management_alerts.map { |alert| { 'title' => alert.title } } - end - - expect(alert_titles).to contain_exactly(*expected_titles) - end - end - - context 'when fetching customer_relations_contacts' do - let(:fields) do - <<~QUERY - nodes { - id - customerRelationsContacts { - nodes { - firstName - } - } - } - QUERY - end - - def clean_state_query - run_with_clean_state(query, context: { current_user: current_user }) - end - - it 'avoids N+1 queries' do - create(:issue_customer_relations_contact, :for_issue, issue: issue_a) - - control = ActiveRecord::QueryRecorder.new(skip_cached: false) { clean_state_query } - - create(:issue_customer_relations_contact, :for_issue, issue: issue_a) - - expect { clean_state_query }.not_to exceed_all_query_limit(control) - end - end - - context 'when fetching labels' do - let(:fields) do - <<~QUERY - nodes { - id - labels { - nodes { - id - } - } - } - QUERY - end - - before do - issues.each do |issue| - # create a label for each issue we have to properly test N+1 - label = create(:label, project: project) - issue.update!(labels: [label]) - end - end - - def response_label_ids(response_data) - response_data.map do |node| - node['labels']['nodes'].map { |u| u['id'] } - end.flatten - end - - def labels_as_global_ids(issues) - issues.map(&:labels).flatten.map(&:to_global_id).map(&:to_s) - end - - it 'avoids N+1 queries', :aggregate_failures do - control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) } - expect(issues_data.count).to eq(5) - expect(response_label_ids(issues_data)).to match_array(labels_as_global_ids(issues)) - - new_issues = issues + [create(:issue, project: project, labels: [create(:label, project: project)])] - - expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control) - # graphql_data is memoized (see spec/support/helpers/graphql_helpers.rb) - # so we have to parse the body ourselves the second time - issues_data = Gitlab::Json.parse(response.body)['data']['project']['issues']['nodes'] - expect(issues_data.count).to eq(6) - expect(response_label_ids(issues_data)).to match_array(labels_as_global_ids(new_issues)) - end - end - - context 'when fetching assignees' do - let(:fields) do - <<~QUERY - nodes { - id - assignees { - nodes { - id - } - } - } - QUERY - end - - before do - issues.each do |issue| - # create an assignee for each issue we have to properly test N+1 - assignee = create(:user) - issue.update!(assignees: [assignee]) - end - end - - def response_assignee_ids(response_data) - response_data.map do |node| - node['assignees']['nodes'].map { |node| node['id'] } - end.flatten - end - - def assignees_as_global_ids(issues) - issues.map(&:assignees).flatten.map(&:to_global_id).map(&:to_s) - end - - it 'avoids N+1 queries', :aggregate_failures do - control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) } - expect(issues_data.count).to eq(5) - expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(issues)) - - new_issues = issues + [create(:issue, project: project, assignees: [create(:user)])] - - expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control) - # graphql_data is memoized (see spec/support/helpers/graphql_helpers.rb) - # so we have to parse the body ourselves the second time - issues_data = Gitlab::Json.parse(response.body)['data']['project']['issues']['nodes'] - expect(issues_data.count).to eq(6) - expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(new_issues)) - end - end - - context 'when fetching escalation status' do - let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: issue_a) } - - let(:statuses) { issue_data.to_h { |issue| [issue['iid'], issue['escalationStatus']] } } - let(:fields) do - <<~QUERY - nodes { - id - escalationStatus - } - QUERY - end - - before do - issue_a.update!(issue_type: Issue.issue_types[:incident]) - end - - it 'returns the escalation status values' do - post_graphql(query, current_user: current_user) - - statuses = issues_data.map { |issue| issue['escalationStatus'] } - - expect(statuses).to contain_exactly(escalation_status.status_name.upcase.to_s, nil, nil, nil, nil) - end - - it 'avoids N+1 queries', :aggregate_failures do - base_count = ActiveRecord::QueryRecorder.new { run_with_clean_state(query, context: { current_user: current_user }) } - - new_incident = create(:incident, project: project) - create(:incident_management_issuable_escalation_status, issue: new_incident) - - expect { run_with_clean_state(query, context: { current_user: current_user }) }.not_to exceed_query_limit(base_count) - end - end - - describe 'N+1 query checks' do - let(:extra_iid_for_second_query) { issue_b.iid.to_s } - let(:search_params) { { iids: [issue_a.iid.to_s] } } - - def execute_query - query = graphql_query_for( - :project, - { full_path: project.full_path }, - query_graphql_field( - :issues, search_params, - query_graphql_field(:nodes, nil, requested_fields) - ) - ) - post_graphql(query, current_user: current_user) - end - - context 'when requesting `user_notes_count`' do - let(:requested_fields) { [:user_notes_count] } - - before do - create_list(:note_on_issue, 2, noteable: issue_a, project: project) - create(:note_on_issue, noteable: issue_b, project: project) - end - - include_examples 'N+1 query check' - end - - context 'when requesting `user_discussions_count`' do - let(:requested_fields) { [:user_discussions_count] } - - before do - create_list(:note_on_issue, 2, noteable: issue_a, project: project) - create(:note_on_issue, noteable: issue_b, project: project) - end - - include_examples 'N+1 query check' - end - - context 'when requesting `merge_requests_count`' do - let(:requested_fields) { [:merge_requests_count] } - - before do - create_list(:merge_requests_closing_issues, 2, issue: issue_a) - create_list(:merge_requests_closing_issues, 3, issue: issue_b) - end - - include_examples 'N+1 query check' - end - - context 'when requesting `timelogs`' do - let(:requested_fields) { 'timelogs { nodes { timeSpent } }' } - - before do - create_list(:issue_timelog, 2, issue: issue_a) - create(:issue_timelog, issue: issue_b) - end - - include_examples 'N+1 query check' - end - - context 'when requesting `closed_as_duplicate_of`' do - let(:requested_fields) { 'closedAsDuplicateOf { id }' } - let(:issue_a_dup) { create(:issue, project: project) } - let(:issue_b_dup) { create(:issue, project: project) } - - before do - issue_a.update!(duplicated_to_id: issue_a_dup) - issue_b.update!(duplicated_to_id: issue_a_dup) - end - - include_examples 'N+1 query check' - end - - context 'when award emoji votes' do - let(:requested_fields) { [:upvotes, :downvotes] } - - before do - create_list(:award_emoji, 2, name: 'thumbsup', awardable: issue_a) - create_list(:award_emoji, 2, name: 'thumbsdown', awardable: issue_b) - end - - include_examples 'N+1 query check' - end - - context 'when requesting participants' do - let_it_be(:issue_c) { create(:issue, project: project) } - - let(:search_params) { { iids: [issue_a.iid.to_s, issue_c.iid.to_s] } } - let(:requested_fields) { 'participants { nodes { name } }' } - - before do - create(:award_emoji, :upvote, awardable: issue_a) - create(:award_emoji, :upvote, awardable: issue_b) - create(:award_emoji, :upvote, awardable: issue_c) - - note_with_emoji_a = create(:note_on_issue, noteable: issue_a, project: project) - note_with_emoji_b = create(:note_on_issue, noteable: issue_b, project: project) - note_with_emoji_c = create(:note_on_issue, noteable: issue_c, project: project) - - create(:award_emoji, :upvote, awardable: note_with_emoji_a) - create(:award_emoji, :upvote, awardable: note_with_emoji_b) - create(:award_emoji, :upvote, awardable: note_with_emoji_c) - end - - # Executes 3 extra queries to fetch participant_attrs - include_examples 'N+1 query check', threshold: 3 - end - - context 'when requesting labels' do - let(:requested_fields) { ['labels { nodes { id } }'] } - - before do - project_labels = create_list(:label, 2, project: project) - group_labels = create_list(:group_label, 2, group: group) - - issue_a.update!(labels: [project_labels.first, group_labels.first].flatten) - issue_b.update!(labels: [project_labels, group_labels].flatten) - end - - include_examples 'N+1 query check', skip_cached: false - end - end - - def issue_ids - graphql_dig_at(issues_data, :id) end def query(params = issue_filter_params) diff --git a/spec/requests/api/graphql/project/jira_import_spec.rb b/spec/requests/api/graphql/project/jira_import_spec.rb index 202220f4bf6..821357b6988 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' -RSpec.describe 'query Jira import data' do +RSpec.describe 'query Jira import data', feature_category: :integrations 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 410d5b21505..3cd689deda5 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' -RSpec.describe 'query Jira projects' do +RSpec.describe 'query Jira projects', feature_category: :integrations do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/project/jira_service_spec.rb b/spec/requests/api/graphql/project/jira_service_spec.rb index d6abe94b873..23f32d2c2d2 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' -RSpec.describe 'query Jira service' do +RSpec.describe 'query Jira service', feature_category: :integrations do include GraphqlHelpers let_it_be(:current_user) { create(:user) } diff --git a/spec/requests/api/graphql/project/job_spec.rb b/spec/requests/api/graphql/project/job_spec.rb index 6edd4cf753f..ba1c8a1f616 100644 --- a/spec/requests/api/graphql/project/job_spec.rb +++ b/spec/requests/api/graphql/project/job_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project.job' do +RSpec.describe 'Query.project.job', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/project/jobs_spec.rb b/spec/requests/api/graphql/project/jobs_spec.rb index 7d0eb203d60..d05d4a2f4b6 100644 --- a/spec/requests/api/graphql/project/jobs_spec.rb +++ b/spec/requests/api/graphql/project/jobs_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'Query.project.jobs' do +RSpec.describe 'Query.project.jobs', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project, :repository, :public) } diff --git a/spec/requests/api/graphql/project/labels_query_spec.rb b/spec/requests/api/graphql/project/labels_query_spec.rb index eeaaaaee575..1930a22ad30 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' -RSpec.describe 'getting project label information' do +RSpec.describe 'getting project label information', feature_category: :team_planning do include GraphqlHelpers let_it_be(:project) { create(:project, :public) } diff --git a/spec/requests/api/graphql/project/languages_spec.rb b/spec/requests/api/graphql/project/languages_spec.rb index 6ef500cde41..88a196c3ff4 100644 --- a/spec/requests/api/graphql/project/languages_spec.rb +++ b/spec/requests/api/graphql/project/languages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project.languages' do +RSpec.describe 'Project.languages', feature_category: :internationalization do include GraphqlHelpers let_it_be(:user) { create(:user) } 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 b1ecb32b365..36e148468bc 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' -RSpec.describe 'getting notes for a merge request' do +RSpec.describe 'getting notes for a merge request', feature_category: :code_review do include GraphqlHelpers let_it_be(:noteable) { create(:merge_request) } diff --git a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb index 4dc272b5c2e..fb7e46cff8e 100644 --- a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb +++ b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project.mergeRequests.pipelines' do +RSpec.describe 'Query.project.mergeRequests.pipelines', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project, :public, :repository) } diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index 6a59df81405..b7aafdf305a 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' -RSpec.describe 'getting merge request information nested in a project' do +RSpec.describe 'getting merge request information nested in a project', feature_category: :code_review do include GraphqlHelpers let_it_be(:project) { create(:project, :repository, :public) } diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index 2895737ae6f..b3b4c8fe0d5 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' -RSpec.describe 'getting merge request listings nested in a project' do +RSpec.describe 'getting merge request listings nested in a project', feature_category: :code_review do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/project/milestones_spec.rb b/spec/requests/api/graphql/project/milestones_spec.rb index a577c367fe5..3b31da77a75 100644 --- a/spec/requests/api/graphql/project/milestones_spec.rb +++ b/spec/requests/api/graphql/project/milestones_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting milestone listings nested in a project' do +RSpec.describe 'getting milestone listings nested in a project', feature_category: :team_planning do include GraphqlHelpers let_it_be(:today) { Time.now.utc.to_date } diff --git a/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb b/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb index 33e1dbcba27..a7d5cc79f1a 100644 --- a/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb +++ b/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting the packages cleanup policy linked to a project' do +RSpec.describe 'getting the packages cleanup policy linked to a project', feature_category: :package_registry do using RSpec::Parameterized::TableSyntax include GraphqlHelpers diff --git a/spec/requests/api/graphql/project/packages_spec.rb b/spec/requests/api/graphql/project/packages_spec.rb index d9ee997eb02..3413b80e8b4 100644 --- a/spec/requests/api/graphql/project/packages_spec.rb +++ b/spec/requests/api/graphql/project/packages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting a package list for a project' do +RSpec.describe 'getting a package list for a project', feature_category: :package_registry do include GraphqlHelpers let_it_be(:resource) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb index 41915d3cdee..0eeb382510e 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' -RSpec.describe 'getting pipeline information nested in a project' do +RSpec.describe 'getting pipeline information nested in a project', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project, :repository, :public) } diff --git a/spec/requests/api/graphql/project/project_members_spec.rb b/spec/requests/api/graphql/project/project_members_spec.rb index 97a79ab3b0e..1f1d8027592 100644 --- a/spec/requests/api/graphql/project/project_members_spec.rb +++ b/spec/requests/api/graphql/project/project_members_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting project members information' do +RSpec.describe 'getting project members information', feature_category: :projects do include GraphqlHelpers let_it_be(:parent_group) { create(:group, :public) } diff --git a/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb b/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb index 39a68d98d84..a13e96eb9d3 100644 --- a/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb +++ b/spec/requests/api/graphql/project/project_pipeline_statistics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'rendering project pipeline statistics' do +RSpec.describe 'rendering project pipeline statistics', feature_category: :continuous_integration do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb index b57c594c64f..d078659b954 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' -RSpec.describe 'rendering project statistics' do +RSpec.describe 'rendering project statistics', feature_category: :project_statistics do include GraphqlHelpers let(:project) { create(:project) } diff --git a/spec/requests/api/graphql/project/recent_issue_boards_query_spec.rb b/spec/requests/api/graphql/project/recent_issue_boards_query_spec.rb index b3daf86c4af..ae459fd26fb 100644 --- a/spec/requests/api/graphql/project/recent_issue_boards_query_spec.rb +++ b/spec/requests/api/graphql/project/recent_issue_boards_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting project recent issue boards' do +RSpec.describe 'getting project recent issue boards', feature_category: :team_planning do include GraphqlHelpers it_behaves_like 'querying a GraphQL type recent boards' do diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb index c4899dbb71e..477388585ca 100644 --- a/spec/requests/api/graphql/project/release_spec.rb +++ b/spec/requests/api/graphql/project/release_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).release(tagName)' do +RSpec.describe 'Query.project(fullPath).release(tagName)', feature_category: :release_orchestration do include GraphqlHelpers include Presentable diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb index c28a6fa7666..aa454349fcf 100644 --- a/spec/requests/api/graphql/project/releases_spec.rb +++ b/spec/requests/api/graphql/project/releases_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.project(fullPath).releases()' do +RSpec.describe 'Query.project(fullPath).releases()', feature_category: :release_orchestration do include GraphqlHelpers let_it_be(:stranger) { create(:user) } diff --git a/spec/requests/api/graphql/project/repository/blobs_spec.rb b/spec/requests/api/graphql/project/repository/blobs_spec.rb index ba87f1100f2..a4ee0910d30 100644 --- a/spec/requests/api/graphql/project/repository/blobs_spec.rb +++ b/spec/requests/api/graphql/project/repository/blobs_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe 'getting blobs in a project repository' do +RSpec.describe 'getting blobs in a project repository', feature_category: :source_code_management do include GraphqlHelpers let(:project) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/project/repository_spec.rb b/spec/requests/api/graphql/project/repository_spec.rb index b00f64c3db6..9f4d69c7b17 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' -RSpec.describe 'getting a repository in a project' do +RSpec.describe 'getting a repository in a project', feature_category: :source_code_management do include GraphqlHelpers let(:project) { create(:project, :repository) } diff --git a/spec/requests/api/graphql/project/runners_spec.rb b/spec/requests/api/graphql/project/runners_spec.rb new file mode 100644 index 00000000000..7304de7bec6 --- /dev/null +++ b/spec/requests/api/graphql/project/runners_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Project.runners', feature_category: :runner do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :public) } + let_it_be(:project) { create(:project, :public, group: group) } + let_it_be(:instance_runner) { create(:ci_runner, :instance) } + let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project]) } + let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) } + let_it_be(:other_project) { create(:project, :repository, :public) } + let_it_be(:other_project_runner) { create(:ci_runner, :project, projects: [other_project]) } + + let_it_be(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + runners { + nodes { + id + } + } + } + } + ) + end + + context 'when the user is a project admin' do + before do + project.add_maintainer(user) + end + + let(:expected_ids) { [project_runner, group_runner, instance_runner].map { |g| g.to_global_id.to_s } } + + it 'returns all runners available to project' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :runners, :nodes).pluck('id')).to match_array(expected_ids) + end + end + + context 'when the user is a project developer' do + before do + project.add_developer(user) + end + + it 'returns no runners' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :runners, :nodes)).to be_empty + end + end + + context 'when on_demand_scans_runner_tags feature flag is disabled' do + before do + stub_feature_flags(on_demand_scans_runner_tags: false) + end + + it 'returns no runners' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :runners, :nodes)).to be_empty + end + end +end diff --git a/spec/requests/api/graphql/project/terraform/state_spec.rb b/spec/requests/api/graphql/project/terraform/state_spec.rb index 5e207ec0963..1889e7a1064 100644 --- a/spec/requests/api/graphql/project/terraform/state_spec.rb +++ b/spec/requests/api/graphql/project/terraform/state_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'query a single terraform state' do +RSpec.describe 'query a single terraform state', feature_category: :infrastructure_as_code do include GraphqlHelpers include ::API::Helpers::RelatedResourcesHelpers diff --git a/spec/requests/api/graphql/project/terraform/states_spec.rb b/spec/requests/api/graphql/project/terraform/states_spec.rb index cc3660bcc6b..25fc07ef509 100644 --- a/spec/requests/api/graphql/project/terraform/states_spec.rb +++ b/spec/requests/api/graphql/project/terraform/states_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'query terraform states' do +RSpec.describe 'query terraform states', feature_category: :infrastructure_as_code do include GraphqlHelpers include ::API::Helpers::RelatedResourcesHelpers diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb index e63e0d3dd04..77b72bf39a1 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' -RSpec.describe 'getting a tree in a project' do +RSpec.describe 'getting a tree in a project', feature_category: :source_code_management do include GraphqlHelpers let_it_be(:project) { create(:project, :repository) } @@ -166,6 +166,54 @@ RSpec.describe 'getting a tree in a project' do end end + context 'when the ref points to a SSH-signed commit' do + let_it_be(:ref) { 'ssh-signed-commit' } + let_it_be(:commit) { project.commit(ref) } + let_it_be(:current_user) { create(:user, email: commit.committer_email).tap { |user| project.add_owner(user) } } + + let(:fields) do + <<~QUERY + tree(path:"#{path}", ref:"#{ref}") { + lastCommit { + signature { + ... on SshSignature { + #{all_graphql_fields_for('SshSignature'.classify, max_depth: 2)} + } + } + } + } + QUERY + end + + let_it_be(:key) do + create(:key, user: current_user, key: extract_public_key_from_commit(commit), expires_at: 2.days.from_now) + end + + def extract_public_key_from_commit(commit) + ssh_commit = Gitlab::Ssh::Commit.new(commit) + signature_data = ::SSHData::Signature.parse_pem(ssh_commit.signature_text) + signature_data.public_key.openssh + end + + before do + post_graphql(query, current_user: current_user) + end + + it 'returns the expected signature data' do + signature = graphql_data['project']['repository']['tree']['lastCommit']['signature'] + + expect(signature['commitSha']).to eq(commit.id) + expect(signature['verificationStatus']).to eq('VERIFIED') + expect(signature['project']['id']).to eq("gid://gitlab/Project/#{project.id}") + expect(signature['user']['id']).to eq("gid://gitlab/User/#{current_user.id}") + expect(signature['key']['id']).to eq("gid://gitlab/Key/#{key.id}") + expect(signature['key']['title']).to eq(key.title) + expect(signature['key']['createdAt']).to be_present + expect(signature['key']['expiresAt']).to be_present + expect(signature['key']['key']).to match(key.key) + end + end + context 'when current user is nil' do it 'returns empty project' do post_graphql(query, current_user: nil) diff --git a/spec/requests/api/graphql/project/work_item_types_spec.rb b/spec/requests/api/graphql/project/work_item_types_spec.rb index 3d30baab816..c31a260c4b8 100644 --- a/spec/requests/api/graphql/project/work_item_types_spec.rb +++ b/spec/requests/api/graphql/project/work_item_types_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting a list of work item types for a project' do +RSpec.describe 'getting a list of work item types for a project', feature_category: :team_planning do include GraphqlHelpers let_it_be(:developer) { create(:user) } diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb index 6d20799c9ec..a59da706a8a 100644 --- a/spec/requests/api/graphql/project/work_items_spec.rb +++ b/spec/requests/api/graphql/project/work_items_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'getting a work item list for a project' do +RSpec.describe 'getting a work item list for a project', feature_category: :team_planning do include GraphqlHelpers let_it_be(:group) { create(:group) } @@ -93,6 +93,11 @@ RSpec.describe 'getting a work item list for a project' do } ... on WorkItemWidgetHierarchy { parent { id } + children { + nodes { + id + } + } } ... on WorkItemWidgetLabels { labels { nodes { id } } @@ -112,6 +117,57 @@ RSpec.describe 'getting a work item list for a project' do end end + context 'when querying WorkItemWidgetHierarchy' do + let_it_be(:children) { create_list(:work_item, 3, :task, project: project) } + let_it_be(:child_link1) { create(:parent_link, work_item_parent: item1, work_item: children[0]) } + + let(:fields) do + <<~GRAPHQL + nodes { + widgets { + type + ... on WorkItemWidgetHierarchy { + hasChildren + parent { id } + children { nodes { id } } + } + } + } + GRAPHQL + end + + it 'executes limited number of N+1 queries' do + post_graphql(query, current_user: current_user) # warm-up + + control = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: current_user) + end + + parent_work_items = create_list(:work_item, 2, project: project) + create(:parent_link, work_item_parent: parent_work_items[0], work_item: children[1]) + create(:parent_link, work_item_parent: parent_work_items[1], work_item: children[2]) + + # There are 2 extra queries for fetching the children field + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/363569 + expect { post_graphql(query, current_user: current_user) } + .not_to exceed_query_limit(control).with_threshold(2) + end + + it 'avoids N+1 queries when children are added to a work item' do + post_graphql(query, current_user: current_user) # warm-up + + control = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: current_user) + end + + create(:parent_link, work_item_parent: item1, work_item: children[1]) + create(:parent_link, work_item_parent: item1, work_item: children[2]) + + expect { post_graphql(query, current_user: current_user) } + .not_to exceed_query_limit(control) + end + end + it_behaves_like 'a working graphql query' do before do post_graphql(query, current_user: current_user) @@ -188,6 +244,60 @@ RSpec.describe 'getting a work item list for a project' do end end + describe 'fetching work item notes widget' do + let(:item_filter_params) { { iid: item2.iid.to_s } } + let(:fields) do + <<~GRAPHQL + edges { + node { + widgets { + type + ... on WorkItemWidgetNotes { + system: discussions(filter: ONLY_ACTIVITY, first: 10) { nodes { id notes { nodes { id system internal body } } } }, + comments: discussions(filter: ONLY_COMMENTS, first: 10) { nodes { id notes { nodes { id system internal body } } } }, + all_notes: discussions(filter: ALL_NOTES, first: 10) { nodes { id notes { nodes { id system internal body } } } } + } + } + } + } + GRAPHQL + end + + before do + create_notes(item1, "some note1") + create_notes(item2, "some note2") + end + + shared_examples 'fetches work item notes' do |user_comments_count:, system_notes_count:| + it "fetches notes" do + post_graphql(query, current_user: current_user) + + all_widgets = graphql_dig_at(items_data, :node, :widgets) + notes_widget = all_widgets.find { |x| x["type"] == "NOTES" } + + all_notes = graphql_dig_at(notes_widget["all_notes"], :nodes) + system_notes = graphql_dig_at(notes_widget["system"], :nodes) + comments = graphql_dig_at(notes_widget["comments"], :nodes) + + expect(comments.count).to eq(user_comments_count) + expect(system_notes.count).to eq(system_notes_count) + expect(all_notes.count).to eq(user_comments_count + system_notes_count) + end + end + + context 'when user has permission to view internal notes' do + before do + project.add_developer(current_user) + end + + it_behaves_like 'fetches work item notes', user_comments_count: 2, system_notes_count: 5 + end + + context 'when user cannot view internal notes' do + it_behaves_like 'fetches work item notes', user_comments_count: 1, system_notes_count: 5 + end + end + def item_ids graphql_dig_at(items_data, :node, :id) end @@ -199,4 +309,26 @@ RSpec.describe 'getting a work item list for a project' do query_graphql_field('workItems', params, fields) ) end + + def create_notes(work_item, note_body) + create(:note, system: true, project: work_item.project, noteable: work_item) + + disc_start = create(:discussion_note_on_issue, noteable: work_item, project: work_item.project, note: note_body) + create(:note, + discussion_id: disc_start.discussion_id, noteable: work_item, + project: work_item.project, note: "reply on #{note_body}") + + create(:resource_label_event, user: current_user, issue: work_item, label: label1, action: 'add') + create(:resource_label_event, user: current_user, issue: work_item, label: label1, action: 'remove') + + create(:resource_milestone_event, issue: work_item, milestone: milestone1, action: 'add') + create(:resource_milestone_event, issue: work_item, milestone: milestone1, action: 'remove') + + # confidential notes are currently available only on issues and epics + conf_disc_start = create(:discussion_note_on_issue, :confidential, + noteable: work_item, project: work_item.project, note: "confidential #{note_body}") + create(:note, :confidential, + discussion_id: conf_disc_start.discussion_id, noteable: work_item, + project: work_item.project, note: "reply on confidential #{note_body}") + end end diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb index d1b990629a1..281a08e6548 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' -RSpec.describe 'getting project information' do +RSpec.describe 'getting project information', feature_category: :projects do include GraphqlHelpers let_it_be(:group) { create(:group) } diff --git a/spec/requests/api/graphql/query_spec.rb b/spec/requests/api/graphql/query_spec.rb index 359c599cd3a..2b9d66ec744 100644 --- a/spec/requests/api/graphql/query_spec.rb +++ b/spec/requests/api/graphql/query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query' do +RSpec.describe 'Query', feature_category: :not_owned 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 d2a45603886..aec8d3151c8 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' -RSpec.describe 'Requests on a read-only node' do +RSpec.describe 'Requests on a read-only node', feature_category: :database do context 'when db is read-only' do before do allow(Gitlab::Database).to receive(:read_only?) { true } diff --git a/spec/requests/api/graphql/snippets_spec.rb b/spec/requests/api/graphql/snippets_spec.rb index 9edd805678a..3cd95b0841c 100644 --- a/spec/requests/api/graphql/snippets_spec.rb +++ b/spec/requests/api/graphql/snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'snippets' do +RSpec.describe 'snippets', feature_category: :source_code_management do include GraphqlHelpers let_it_be(:current_user) { create(:user) } 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 5f4d2aec718..ea89487c176 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' -RSpec.describe 'getting task completion status information' do +RSpec.describe 'getting task completion status information', feature_category: :team_planning do include GraphqlHelpers description_0_done = '- [ ] task 1\n- [ ] task 2' diff --git a/spec/requests/api/graphql/terraform/state/delete_spec.rb b/spec/requests/api/graphql/terraform/state/delete_spec.rb index ba0619ea611..f4af402492c 100644 --- a/spec/requests/api/graphql/terraform/state/delete_spec.rb +++ b/spec/requests/api/graphql/terraform/state/delete_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'delete a terraform state' do +RSpec.describe 'delete a terraform state', feature_category: :infrastructure_as_code do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/terraform/state/lock_spec.rb b/spec/requests/api/graphql/terraform/state/lock_spec.rb index e4d3b6336ab..4219f4f4651 100644 --- a/spec/requests/api/graphql/terraform/state/lock_spec.rb +++ b/spec/requests/api/graphql/terraform/state/lock_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'lock a terraform state' do +RSpec.describe 'lock a terraform state', feature_category: :infrastructure_as_code do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/terraform/state/unlock_spec.rb b/spec/requests/api/graphql/terraform/state/unlock_spec.rb index e90730f2d8f..84ccc09711d 100644 --- a/spec/requests/api/graphql/terraform/state/unlock_spec.rb +++ b/spec/requests/api/graphql/terraform/state/unlock_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'unlock a terraform state' do +RSpec.describe 'unlock a terraform state', feature_category: :infrastructure_as_code do include GraphqlHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/graphql/todo_query_spec.rb b/spec/requests/api/graphql/todo_query_spec.rb index 7fe19448083..b8ce065dbbb 100644 --- a/spec/requests/api/graphql/todo_query_spec.rb +++ b/spec/requests/api/graphql/todo_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Todo Query' do +RSpec.describe 'Todo Query', feature_category: :team_planning do include GraphqlHelpers let_it_be(:current_user) { nil } diff --git a/spec/requests/api/graphql/usage_trends_measurements_spec.rb b/spec/requests/api/graphql/usage_trends_measurements_spec.rb index 78a4321f522..68b97fbb130 100644 --- a/spec/requests/api/graphql/usage_trends_measurements_spec.rb +++ b/spec/requests/api/graphql/usage_trends_measurements_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'UsageTrendsMeasurements' do +RSpec.describe 'UsageTrendsMeasurements', feature_category: :devops_reports do include GraphqlHelpers let(:current_user) { create(:user, :admin) } 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 e47cef8cc37..d09cb319877 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' -RSpec.describe 'GroupMember' do +RSpec.describe 'GroupMember', feature_category: :subgroups 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 01827e94d5d..1baa7815793 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' -RSpec.describe 'ProjectMember' do +RSpec.describe 'ProjectMember', feature_category: :subgroups do include GraphqlHelpers let_it_be(:member) { create(:project_member, :developer) } diff --git a/spec/requests/api/graphql/user/starred_projects_query_spec.rb b/spec/requests/api/graphql/user/starred_projects_query_spec.rb index 75a17ed34c4..7d4284300d8 100644 --- a/spec/requests/api/graphql/user/starred_projects_query_spec.rb +++ b/spec/requests/api/graphql/user/starred_projects_query_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Getting starredProjects of the user' do +RSpec.describe 'Getting starredProjects of the user', feature_category: :projects do include GraphqlHelpers let(:query) do diff --git a/spec/requests/api/graphql/user_query_spec.rb b/spec/requests/api/graphql/user_query_spec.rb index 8f286180617..ca319ed1b2e 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' -RSpec.describe 'getting user information' do +RSpec.describe 'getting user information', feature_category: :user_management 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 a3b2b750bc3..2e1e4971767 100644 --- a/spec/requests/api/graphql/user_spec.rb +++ b/spec/requests/api/graphql/user_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User' do +RSpec.describe 'User', feature_category: :users 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 79ee3c2cb57..83c360fdaf8 100644 --- a/spec/requests/api/graphql/users_spec.rb +++ b/spec/requests/api/graphql/users_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Users' do +RSpec.describe 'Users', feature_category: :user_management do include GraphqlHelpers let_it_be(:user0) { create(:user, created_at: 1.day.ago) } diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb index a55de6adfb2..df7dbaea420 100644 --- a/spec/requests/api/graphql/work_item_spec.rb +++ b/spec/requests/api/graphql/work_item_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Query.work_item(id)' do +RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do include GraphqlHelpers let_it_be(:developer) { create(:user) } @@ -116,6 +116,7 @@ RSpec.describe 'Query.work_item(id)' do id } } + hasChildren } } GRAPHQL @@ -132,7 +133,8 @@ RSpec.describe 'Query.work_item(id)' do [ hash_including('id' => child_link1.work_item.to_gid.to_s), hash_including('id' => child_link2.work_item.to_gid.to_s) - ]) } + ]) }, + 'hasChildren' => true ) ) ) @@ -165,7 +167,8 @@ RSpec.describe 'Query.work_item(id)' do 'children' => { 'nodes' => match_array( [ hash_including('id' => child_link1.work_item.to_gid.to_s) - ]) } + ]) }, + 'hasChildren' => true ) ) ) @@ -183,7 +186,8 @@ RSpec.describe 'Query.work_item(id)' do hash_including( 'type' => 'HIERARCHY', 'parent' => hash_including('id' => parent_link.work_item_parent.to_gid.to_s), - 'children' => { 'nodes' => match_array([]) } + 'children' => { 'nodes' => match_array([]) }, + 'hasChildren' => false ) ) ) diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb index 1c1ae73ddfe..d7724371cce 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' -RSpec.describe 'GraphQL' do +RSpec.describe 'GraphQL', feature_category: :not_owned do include GraphqlHelpers include AfterNextHelpers @@ -25,12 +25,12 @@ RSpec.describe 'GraphQL' do "query_analysis.used_fields" => ['Query.echo'], "query_analysis.used_deprecated_fields" => [], # query_fingerprint starts with operation name - query_fingerprint: %r{^anonymous\/}, + query_fingerprint: %r{^anonymous/}, duration_s: kind_of(Numeric), trace_type: 'execute_query', operation_name: nil, # operation_fingerprint starts with operation name - operation_fingerprint: %r{^anonymous\/}, + operation_fingerprint: %r{^anonymous/}, is_mutation: false, variables: variables.to_s, query_string: query diff --git a/spec/requests/api/group_avatar_spec.rb b/spec/requests/api/group_avatar_spec.rb index 50379d29b09..9a0e79ee9f8 100644 --- a/spec/requests/api/group_avatar_spec.rb +++ b/spec/requests/api/group_avatar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupAvatar do +RSpec.describe API::GroupAvatar, feature_category: :subgroups do def avatar_path(group) "/groups/#{ERB::Util.url_encode(group.full_path)}/avatar" end diff --git a/spec/requests/api/group_boards_spec.rb b/spec/requests/api/group_boards_spec.rb index cc110aa4017..01f0e6e2061 100644 --- a/spec/requests/api/group_boards_spec.rb +++ b/spec/requests/api/group_boards_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupBoards do +RSpec.describe API::GroupBoards, feature_category: :team_planning 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 8e127bf0710..68c3af01e56 100644 --- a/spec/requests/api/group_clusters_spec.rb +++ b/spec/requests/api/group_clusters_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupClusters do +RSpec.describe API::GroupClusters, feature_category: :kubernetes_management do include KubernetesHelpers let(:current_user) { create(:user) } diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb index 82daab0e5e8..cd88b060a3a 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' -RSpec.describe API::GroupContainerRepositories do +RSpec.describe API::GroupContainerRepositories, feature_category: :container_registry do let_it_be(:group) { create(:group, :private) } let_it_be(:project) { create(:project, :private, group: group) } let_it_be(:reporter) { create(:user) } @@ -35,7 +35,9 @@ RSpec.describe API::GroupContainerRepositories do describe 'GET /groups/:id/registry/repositories' do let(:url) { "/groups/#{group.id}/registry/repositories" } - let(:snowplow_gitlab_standard_context) { { user: api_user, namespace: group } } + let(:snowplow_gitlab_standard_context) do + { user: api_user, namespace: group, property: 'i_package_container_user' } + end subject { get api(url, api_user) } diff --git a/spec/requests/api/group_debian_distributions_spec.rb b/spec/requests/api/group_debian_distributions_spec.rb index 21c5f2f09a0..57b481e4f9f 100644 --- a/spec/requests/api/group_debian_distributions_spec.rb +++ b/spec/requests/api/group_debian_distributions_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::GroupDebianDistributions do +RSpec.describe API::GroupDebianDistributions, feature_category: :package_registry do include HttpBasicAuthHelpers include WorkhorseHelpers diff --git a/spec/requests/api/group_export_spec.rb b/spec/requests/api/group_export_spec.rb index 83c34204c78..565365506a7 100644 --- a/spec/requests/api/group_export_spec.rb +++ b/spec/requests/api/group_export_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupExport do +RSpec.describe API::GroupExport, feature_category: :importers do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/group_import_spec.rb b/spec/requests/api/group_import_spec.rb index efad6334518..07c21c93585 100644 --- a/spec/requests/api/group_import_spec.rb +++ b/spec/requests/api/group_import_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupImport do +RSpec.describe API::GroupImport, feature_category: :importers do include WorkhorseHelpers include_context 'workhorse headers' @@ -198,12 +198,6 @@ RSpec.describe API::GroupImport do include_examples 'when all params are correct' include_examples 'when some params are missing' end - - it "doesn't attempt to migrate file to object storage" do - expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async) - - subject - end end context 'with object storage enabled' do diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb index 34533da53dd..1dd90413d35 100644 --- a/spec/requests/api/group_labels_spec.rb +++ b/spec/requests/api/group_labels_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupLabels do +RSpec.describe API::GroupLabels, feature_category: :team_planning do let_it_be(:valid_group_label_title_1) { 'Label foo & bar:subgroup::v.1' } let_it_be(:valid_group_label_title_1_esc) { ERB::Util.url_encode(valid_group_label_title_1) } let_it_be(:valid_group_label_title_2) { 'Bar & foo:subgroup::v.2' } diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb index da84e98b905..91f64d02d43 100644 --- a/spec/requests/api/group_milestones_spec.rb +++ b/spec/requests/api/group_milestones_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupMilestones do +RSpec.describe API::GroupMilestones, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :private) } let_it_be(:project) { create(:project, namespace: group) } diff --git a/spec/requests/api/group_packages_spec.rb b/spec/requests/api/group_packages_spec.rb index a2b0b35c76a..0b4f6130132 100644 --- a/spec/requests/api/group_packages_spec.rb +++ b/spec/requests/api/group_packages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupPackages do +RSpec.describe API::GroupPackages, feature_category: :package_registry 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) } diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb index a07a8ae4292..90b9606ec7b 100644 --- a/spec/requests/api/group_variables_spec.rb +++ b/spec/requests/api/group_variables_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::GroupVariables do +RSpec.describe API::GroupVariables, feature_category: :pipeline_authoring do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } let_it_be(:variable) { create(:ci_group_variable, group: group) } diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index c94bc1e1bac..12a6553f51a 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Groups do +RSpec.describe API::Groups, feature_category: :subgroups do include GroupAPIHelpers include UploadHelpers include WorkhorseHelpers diff --git a/spec/requests/api/helm_packages_spec.rb b/spec/requests/api/helm_packages_spec.rb index 6bd81f64913..584f6e3c7d4 100644 --- a/spec/requests/api/helm_packages_spec.rb +++ b/spec/requests/api/helm_packages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::HelmPackages do +RSpec.describe API::HelmPackages, feature_category: :package_registry do include_context 'helm api setup' using RSpec::Parameterized::TableSyntax @@ -17,6 +17,8 @@ RSpec.describe API::HelmPackages do let_it_be(:package_file2_2) { create(:helm_package_file, package: package2, file_sha256: 'file2', file_name: 'filename2.tgz', channel: 'test', description: 'hello from test channel') } let_it_be(:other_package) { create(:npm_package, project: project) } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_helm_user' } } + describe 'GET /api/v4/projects/:id/packages/helm/:channel/index.yaml' do let(:project_id) { project.id } let(:channel) { 'stable' } @@ -63,7 +65,6 @@ RSpec.describe API::HelmPackages do with_them do let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } before do project.update!(visibility: visibility.to_s) @@ -74,8 +75,6 @@ RSpec.describe API::HelmPackages do end context 'with access to package registry for everyone' do - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } - before do project.update!(visibility: Gitlab::VisibilityLevel::PRIVATE) project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) @@ -152,7 +151,6 @@ RSpec.describe API::HelmPackages do let(:params) { { chart: temp_file(file_name) } } let(:file_key) { :chart } let(:send_rewritten_field) { true } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } subject do workhorse_finalize( diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index e29e5c31a34..38275ce0057 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' -RSpec.describe API::Helpers do +RSpec.describe API::Helpers, :enable_admin_mode, feature_category: :authentication_and_authorization 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 index 8ab41f49549..7c2df52fdf3 100644 --- a/spec/requests/api/import_bitbucket_server_spec.rb +++ b/spec/requests/api/import_bitbucket_server_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ImportBitbucketServer do +RSpec.describe API::ImportBitbucketServer, feature_category: :importers do let(:base_uri) { "https://test:7990" } let(:user) { create(:user) } let(:token) { "asdasd12345" } diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb index 4f95295c14d..dce82f1cf37 100644 --- a/spec/requests/api/import_github_spec.rb +++ b/spec/requests/api/import_github_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ImportGithub do +RSpec.describe API::ImportGithub, feature_category: :importers do let(:token) { "asdasd12345" } let(:provider) { :github } let(:access_params) { { github_access_token: token } } diff --git a/spec/requests/api/integrations/jira_connect/subscriptions_spec.rb b/spec/requests/api/integrations/jira_connect/subscriptions_spec.rb index 8a222a99b34..8d7b462771d 100644 --- a/spec/requests/api/integrations/jira_connect/subscriptions_spec.rb +++ b/spec/requests/api/integrations/jira_connect/subscriptions_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Integrations::JiraConnect::Subscriptions do +RSpec.describe API::Integrations::JiraConnect::Subscriptions, feature_category: :integrations do describe 'POST /integrations/jira_connect/subscriptions' do subject(:post_subscriptions) { post api('/integrations/jira_connect/subscriptions') } @@ -20,20 +20,6 @@ RSpec.describe API::Integrations::JiraConnect::Subscriptions do post api('/integrations/jira_connect/subscriptions', user), params: { jwt: jwt, namespace_path: group.path } end - context 'with feature flag disabled' do - before do - stub_feature_flags(jira_connect_oauth: false) - end - - let(:jwt) { '123' } - - it 'returns 404' do - post_subscriptions - - expect(response).to have_gitlab_http_status(:not_found) - end - end - context 'with invalid JWT' do let(:jwt) { '123' } diff --git a/spec/requests/api/integrations_spec.rb b/spec/requests/api/integrations_spec.rb index 1e8061f9606..c35b9bab0ec 100644 --- a/spec/requests/api/integrations_spec.rb +++ b/spec/requests/api/integrations_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe API::Integrations do +RSpec.describe API::Integrations, feature_category: :integrations do let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index 32cacfc713c..f9284f21aaa 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::Base do +RSpec.describe API::Internal::Base, feature_category: :authentication_and_authorization do include GitlabShellHelpers include APIInternalBaseHelpers @@ -325,6 +325,28 @@ RSpec.describe API::Internal::Base do expect(json_response['name']).to eq(user.name) end + context 'when signing key is passed' do + it 'does not authenticate user' do + key.signing! + + get(api("/internal/discover"), params: { key_id: key.id }, headers: gitlab_shell_internal_api_request_header) + + expect(json_response).to be_nil + end + end + + context 'when auth-only key is passed' do + it 'authenticates user' do + key.auth! + + get(api("/internal/discover"), params: { key_id: key.id }, headers: gitlab_shell_internal_api_request_header) + + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response['name']).to eq(user.name) + end + end + it "finds a user by username" do get(api("/internal/discover"), params: { username: user.username }, headers: gitlab_shell_internal_api_request_header) @@ -360,6 +382,30 @@ RSpec.describe API::Internal::Base do expect(json_response['key'].split[1]).to eq(key.key.split[1]) end + context 'when signing key is passed' do + it 'does not return the key' do + key.signing! + + get(api('/internal/authorized_keys'), params: { key: key.key.split[1] }, headers: gitlab_shell_internal_api_request_header) + + expect(response).to have_gitlab_http_status(:not_found) + + expect(json_response['id']).to be_nil + end + end + + context 'when auth-only key is passed' do + it 'authenticates user' do + key.auth! + + get(api('/internal/authorized_keys'), params: { key: key.key.split[1] }, headers: gitlab_shell_internal_api_request_header) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['id']).to eq(key.id) + expect(json_response['key'].split[1]).to eq(key.key.split[1]) + end + end + it 'exposes the comment of the key as a simple identifier of username + hostname' do get(api('/internal/authorized_keys'), params: { key: key.key.split[1] }, headers: gitlab_shell_internal_api_request_header) diff --git a/spec/requests/api/internal/container_registry/migration_spec.rb b/spec/requests/api/internal/container_registry/migration_spec.rb index db2918e65f1..b9258e4627a 100644 --- a/spec/requests/api/internal/container_registry/migration_spec.rb +++ b/spec/requests/api/internal/container_registry/migration_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::ContainerRegistry::Migration, :aggregate_failures do +RSpec.describe API::Internal::ContainerRegistry::Migration, :aggregate_failures, feature_category: :database do let_it_be_with_reload(:repository) { create(:container_repository) } let(:secret_token) { 'secret_token' } diff --git a/spec/requests/api/internal/error_tracking_spec.rb b/spec/requests/api/internal/error_tracking_spec.rb index 4c420eb8505..83012e26138 100644 --- a/spec/requests/api/internal/error_tracking_spec.rb +++ b/spec/requests/api/internal/error_tracking_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::ErrorTracking do +RSpec.describe API::Internal::ErrorTracking, feature_category: :error_tracking do let(:secret_token) { Gitlab::CurrentSettings.error_tracking_access_token } let(:headers) do { ::API::Internal::ErrorTracking::GITLAB_ERROR_TRACKING_TOKEN_HEADER => secret_token } diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb index 3c6604cf409..dc631ad7921 100644 --- a/spec/requests/api/internal/kubernetes_spec.rb +++ b/spec/requests/api/internal/kubernetes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::Kubernetes do +RSpec.describe API::Internal::Kubernetes, feature_category: :kubernetes_management do let(:jwt_auth_headers) do jwt_token = JWT.encode({ 'iss' => Gitlab::Kas::JWT_ISSUER }, Gitlab::Kas.secret, 'HS256') @@ -103,15 +103,19 @@ RSpec.describe API::Internal::Kubernetes do expect(response).to have_gitlab_http_status(:bad_request) end - it 'tracks events' do + it 'tracks events and unique events', :aggregate_failures do + request_count = 2 counters = { gitops_sync: 10, k8s_api_proxy_request: 5 } - unique_counters = { agent_users_using_ci_tunnel: [10] } + unique_counters = { agent_users_using_ci_tunnel: [10, 999, 777, 10] } expected_counters = { - kubernetes_agent_gitops_sync: counters[:gitops_sync], - kubernetes_agent_k8s_api_proxy_request: counters[:k8s_api_proxy_request] + kubernetes_agent_gitops_sync: request_count * counters[:gitops_sync], + kubernetes_agent_k8s_api_proxy_request: request_count * counters[:k8s_api_proxy_request] } + expected_hll_count = unique_counters[:agent_users_using_ci_tunnel].uniq.count - send_request(params: { counters: counters, unique_counters: unique_counters }) + request_count.times do + send_request(params: { counters: counters, unique_counters: unique_counters }) + end expect(Gitlab::UsageDataCounters::KubernetesAgentCounter.totals).to eq(expected_counters) @@ -121,7 +125,7 @@ RSpec.describe API::Internal::Kubernetes do event_names: 'agent_users_using_ci_tunnel', start_date: Date.current, end_date: Date.current + 10 ) - ).to eq(1) + ).to eq(expected_hll_count) end end end diff --git a/spec/requests/api/internal/lfs_spec.rb b/spec/requests/api/internal/lfs_spec.rb index 9eb48db5bd5..1021c03f736 100644 --- a/spec/requests/api/internal/lfs_spec.rb +++ b/spec/requests/api/internal/lfs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::Lfs do +RSpec.describe API::Internal::Lfs, feature_category: :source_code_management do include GitlabShellHelpers include APIInternalBaseHelpers diff --git a/spec/requests/api/internal/mail_room_spec.rb b/spec/requests/api/internal/mail_room_spec.rb index a0a9c1f9cb3..7baa26e3508 100644 --- a/spec/requests/api/internal/mail_room_spec.rb +++ b/spec/requests/api/internal/mail_room_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::MailRoom do +RSpec.describe API::Internal::MailRoom, feature_category: :service_desk do let(:base_configs) do { enabled: true, diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb index 5b970ca605c..56f1089843b 100644 --- a/spec/requests/api/internal/pages_spec.rb +++ b/spec/requests/api/internal/pages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::Pages do +RSpec.describe API::Internal::Pages, feature_category: :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/internal/workhorse_spec.rb b/spec/requests/api/internal/workhorse_spec.rb index bcf63bf7c2f..99d0ecabbb7 100644 --- a/spec/requests/api/internal/workhorse_spec.rb +++ b/spec/requests/api/internal/workhorse_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Internal::Workhorse, :allow_forgery_protection do +RSpec.describe API::Internal::Workhorse, :allow_forgery_protection, feature_category: :not_owned do include WorkhorseHelpers context '/authorize_upload' do diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb index c07d2e11363..9d3ab269ca1 100644 --- a/spec/requests/api/invitations_spec.rb +++ b/spec/requests/api/invitations_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Invitations do +RSpec.describe API::Invitations, feature_category: :users do let_it_be(:maintainer) { create(:user, username: 'maintainer_user') } let_it_be(:maintainer2) { create(:user, username: 'user-with-maintainer-role') } let_it_be(:developer) { create(:user) } diff --git a/spec/requests/api/issue_links_spec.rb b/spec/requests/api/issue_links_spec.rb index 98f72f22cdc..93bf17d72d7 100644 --- a/spec/requests/api/issue_links_spec.rb +++ b/spec/requests/api/issue_links_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::IssueLinks do +RSpec.describe API::IssueLinks, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:issue) { create(:issue, project: project) } @@ -189,6 +189,8 @@ RSpec.describe API::IssueLinks do end context 'when authenticated' do + let_it_be(:target_issue) { create(:issue, project: project) } + context 'when issue link does not exist' do it 'returns 404' do perform_request(non_existing_record_id, user) @@ -197,8 +199,6 @@ RSpec.describe API::IssueLinks do end end - let_it_be(:target_issue) { create(:issue, project: project) } - context 'when issue link does not belong to the specified issue' do it 'returns 404' do other_issue = create(:issue, project: project) diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb index 5c06214316b..0641c2135c1 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' -RSpec.describe API::Issues do +RSpec.describe API::Issues, feature_category: :team_planning 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 ec6cc060c83..70966d23576 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' -RSpec.describe API::Issues do +RSpec.describe API::Issues, feature_category: :team_planning 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 0e20b2133db..94f0443e14a 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Issues do +RSpec.describe API::Issues, feature_category: :team_planning do using RSpec::Parameterized::TableSyntax let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb index deaf7be96ab..7305da1305a 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' -RSpec.describe API::Issues do +RSpec.describe API::Issues, feature_category: :team_planning 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 d6c57b460e0..2d7439d65c1 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' -RSpec.describe API::Issues do +RSpec.describe API::Issues, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:owner) { create(:owner) } let(:user2) { create(:user) } diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index 67c3de324dc..d9a0f061156 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Keys do +RSpec.describe API::Keys, feature_category: :authentication_and_authorization do let_it_be(:user) { create(:user) } let_it_be(:admin) { create(:admin) } let_it_be(:email) { create(:email, user: user) } diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 97ab90c9776..b5d7d564749 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Labels do +RSpec.describe API::Labels, feature_category: :team_planning 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 5d8ed3dd0f5..82b87007a9b 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Lint do +RSpec.describe API::Lint, feature_category: :pipeline_authoring do describe 'POST /ci/lint' do context 'when signup settings are disabled' do before do diff --git a/spec/requests/api/markdown_golden_master_spec.rb b/spec/requests/api/markdown_golden_master_spec.rb index 4fa946de342..1bb5a1d67ae 100644 --- a/spec/requests/api/markdown_golden_master_spec.rb +++ b/spec/requests/api/markdown_golden_master_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' # See spec/fixtures/markdown/markdown_golden_master_examples.yml for documentation on how this spec works. -RSpec.describe API::Markdown, 'Golden Master' do +RSpec.describe API::Markdown, 'Golden Master', feature_category: :team_planning do markdown_yml_file_path = File.expand_path('../../fixtures/markdown/markdown_golden_master_examples.yml', __dir__) include_context 'API::Markdown Golden Master shared context', markdown_yml_file_path end diff --git a/spec/requests/api/markdown_snapshot_spec.rb b/spec/requests/api/markdown_snapshot_spec.rb index f2019172a54..866cbcf8ff6 100644 --- a/spec/requests/api/markdown_snapshot_spec.rb +++ b/spec/requests/api/markdown_snapshot_spec.rb @@ -4,6 +4,6 @@ require 'spec_helper' # See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#markdown-snapshot-testing # for documentation on this spec. -RSpec.describe API::Markdown, 'Snapshot' do +RSpec.describe API::Markdown, 'Snapshot', feature_category: :team_planning do include_context 'with API::Markdown Snapshot shared context' end diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb index 6239ac4e749..db5bbd610fc 100644 --- a/spec/requests/api/markdown_spec.rb +++ b/spec/requests/api/markdown_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe API::Markdown do +RSpec.describe API::Markdown, feature_category: :team_planning do describe "POST /markdown" do let(:user) {} # No-op. It gets overwritten in the contexts below. let(:disable_authenticate_markdown_api) { false } diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb index ac8c4aacdf2..092eb442f1f 100644 --- a/spec/requests/api/maven_packages_spec.rb +++ b/spec/requests/api/maven_packages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::MavenPackages do +RSpec.describe API::MavenPackages, feature_category: :package_registry do using RSpec::Parameterized::TableSyntax include WorkhorseHelpers @@ -22,7 +22,8 @@ RSpec.describe API::MavenPackages do let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) } let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_maven_user' } } + let(:package_name) { 'com/example/my-app' } let(:headers) { workhorse_headers } let(:headers_with_token) { headers.merge('Private-Token' => personal_access_token.token) } @@ -98,8 +99,6 @@ RSpec.describe API::MavenPackages do context 'with jar file' do let_it_be(:package_file) { jar_file } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } - it_behaves_like 'a package tracking event', described_class.name, 'pull_package' end end @@ -900,6 +899,8 @@ RSpec.describe API::MavenPackages do it_behaves_like 'package workhorse uploads' context 'event tracking' do + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_maven_user' } } + it_behaves_like 'a package tracking event', described_class.name, 'push_package' context 'when the package file fails to be created' do diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 69be574f38a..4eff5e96e9c 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Members do +RSpec.describe API::Members, feature_category: :subgroups do let_it_be(:maintainer) { create(:user, username: 'maintainer_user') } let_it_be(:maintainer2) { create(:user, username: 'user-with-maintainer-role') } let_it_be(:developer) { create(:user) } diff --git a/spec/requests/api/merge_request_approvals_spec.rb b/spec/requests/api/merge_request_approvals_spec.rb index b18f3017e03..a1d6abec97e 100644 --- a/spec/requests/api/merge_request_approvals_spec.rb +++ b/spec/requests/api/merge_request_approvals_spec.rb @@ -2,8 +2,10 @@ require 'spec_helper' -RSpec.describe API::MergeRequestApprovals do +RSpec.describe API::MergeRequestApprovals, feature_category: :source_code_management do let_it_be(:user) { create(:user) } + let_it_be(:user2) { create(:user) } + let_it_be(:bot) { create(:user, :project_bot) } let_it_be(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) } let_it_be(:approver) { create :user } let_it_be(:group) { create :group } @@ -87,4 +89,83 @@ RSpec.describe API::MergeRequestApprovals do end end end + + describe 'PUT :id/merge_requests/:merge_request_iid/reset_approvals' do + before do + merge_request.approvals.create!(user: user2) + create(:project_member, :maintainer, user: bot, source: project) + end + + context 'for a bot user' do + it 'clears approvals of the merge_request' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", bot) + + merge_request.reload + expect(response).to have_gitlab_http_status(:accepted) + expect(merge_request.approvals).to be_empty + end + + context 'when bot user approved the merge request' do + before do + merge_request.approvals.create!(user: bot) + end + + it 'clears approvals of the merge_request' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", bot) + + merge_request.reload + expect(response).to have_gitlab_http_status(:accepted) + expect(merge_request.approvals).to be_empty + end + end + end + + context 'for users with non-bot roles' do + let(:human_user) { create(:user) } + + [:add_owner, :add_maintainer, :add_developer, :add_guest].each do |role_method| + it 'returns 401' do + project.send(role_method, human_user) + + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", human_user) + + merge_request.reload + expect(response).to have_gitlab_http_status(:unauthorized) + expect(merge_request.approvals.pluck(:user_id)).to contain_exactly(user2.id) + end + end + end + + context 'for bot-users from external namespaces' do + let_it_be(:external_bot) { create(:user, :project_bot) } + + context 'for external group bot-user' do + before do + create(:group_member, :maintainer, user: external_bot, source: create(:group)) + end + + it 'returns 401' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", external_bot) + + merge_request.reload + expect(response).to have_gitlab_http_status(:unauthorized) + expect(merge_request.approvals.pluck(:user_id)).to contain_exactly(user2.id) + end + end + + context 'for external project bot-user' do + before do + create(:project_member, :maintainer, user: external_bot, source: create(:project)) + end + + it 'returns 401' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", external_bot) + + merge_request.reload + expect(response).to have_gitlab_http_status(:unauthorized) + expect(merge_request.approvals.pluck(:user_id)).to contain_exactly(user2.id) + end + 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 caef946273a..4f812e5d8eb 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" -RSpec.describe API::MergeRequestDiffs, 'MergeRequestDiffs' do +RSpec.describe API::MergeRequestDiffs, 'MergeRequestDiffs', feature_category: :source_code_management 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 eea223485ce..0b69000ae7e 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -2,14 +2,13 @@ require "spec_helper" -RSpec.describe API::MergeRequests do +RSpec.describe API::MergeRequests, feature_category: :source_code_management do include ProjectForksHelper let_it_be(:base_time) { Time.now } let_it_be(:user) { create(:user) } let_it_be(:user2) { create(:user) } let_it_be(:admin) { create(:user, :admin) } - let_it_be(:bot) { create(:user, :project_bot) } let_it_be(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) } let(:milestone1) { create(:milestone, title: '0.9', project: project) } @@ -1788,6 +1787,58 @@ RSpec.describe API::MergeRequests do end end + describe 'GET /projects/:id/merge_requests/:merge_request_iid/diffs' do + let_it_be(:merge_request) do + create( + :merge_request, + :simple, + author: user, + assignees: [user], + source_project: project, + target_project: project, + source_branch: 'markdown', + title: "Test", + created_at: base_time + ) + end + + it 'returns a 404 when merge_request_iid not found' do + get api("/projects/#{project.id}/merge_requests/0/diffs", user) + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'returns a 404 when merge_request id is used instead of iid' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/diffs", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + + context 'when merge request author has only guest access' do + it_behaves_like 'rejects user from accessing merge request info' do + let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/diffs" } + end + end + + it 'returns the diffs of the merge_request' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/diffs", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.size).to eq(merge_request.diffs.size) + end + + context 'when pagination params are present' do + it 'returns limited diffs' do + get( + api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/diffs", user), + params: { page: 1, per_page: 1 } + ) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.size).to eq(1) + end + end + end + describe 'GET /projects/:id/merge_requests/:merge_request_iid/pipelines' do let_it_be(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, target_project: project, source_branch: 'markdown', title: "Test", created_at: base_time) } @@ -3560,71 +3611,6 @@ RSpec.describe API::MergeRequests do end end - describe 'PUT :id/merge_requests/:merge_request_iid/reset_approvals' do - before do - merge_request.approvals.create!(user: user2) - create(:project_member, :maintainer, user: bot, source: project) - end - - context 'when reset_approvals can be performed' do - it 'clears approvals of the merge_request' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", bot) - - merge_request.reload - expect(response).to have_gitlab_http_status(:accepted) - expect(merge_request.approvals).to be_empty - end - - it 'for users with bot role' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", bot) - - expect(response).to have_gitlab_http_status(:accepted) - end - - context 'for users with non-bot roles' do - let(:human_user) { create(:user) } - - [:add_owner, :add_maintainer, :add_developer, :add_guest].each do |role_method| - it 'returns 401' do - project.send(role_method, human_user) - - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", human_user) - - expect(response).to have_gitlab_http_status(:unauthorized) - end - end - end - - context 'for bot-users from external namespaces' do - let_it_be(:external_bot) { create(:user, :project_bot) } - - context 'external group bot-user' do - before do - create(:group_member, :maintainer, user: external_bot, source: create(:group)) - end - - it 'returns 401' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", external_bot) - - expect(response).to have_gitlab_http_status(:unauthorized) - end - end - - context 'external project bot-user' do - before do - create(:project_member, :maintainer, user: external_bot, source: create(:project)) - end - - it 'returns 401' do - put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/reset_approvals", external_bot) - - expect(response).to have_gitlab_http_status(:unauthorized) - end - end - end - end - end - describe 'Time tracking' do let!(:issuable) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, target_project: project, source_branch: 'markdown', title: "Test", created_at: base_time) } diff --git a/spec/requests/api/metadata_spec.rb b/spec/requests/api/metadata_spec.rb index 5b6407c689b..b9bdadb01cc 100644 --- a/spec/requests/api/metadata_spec.rb +++ b/spec/requests/api/metadata_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Metadata do +RSpec.describe API::Metadata, feature_category: :not_owned do shared_examples_for 'GET /metadata' do context 'when unauthenticated' do it 'returns authentication error' do diff --git a/spec/requests/api/metrics/dashboard/annotations_spec.rb b/spec/requests/api/metrics/dashboard/annotations_spec.rb index a09596f167d..7932dd29e4d 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' -RSpec.describe API::Metrics::Dashboard::Annotations do +RSpec.describe API::Metrics::Dashboard::Annotations, feature_category: :metrics 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 7f019e1226a..38d3c0be8b2 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' -RSpec.describe API::Metrics::UserStarredDashboards do +RSpec.describe API::Metrics::UserStarredDashboards, feature_category: :metrics 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/ml/mlflow_spec.rb b/spec/requests/api/ml/mlflow_spec.rb index 9448f009742..c1ed7d56ba4 100644 --- a/spec/requests/api/ml/mlflow_spec.rb +++ b/spec/requests/api/ml/mlflow_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require 'mime/types' -RSpec.describe API::Ml::Mlflow do +RSpec.describe API::Ml::Mlflow, feature_category: :mlops do include SessionHelpers include ApiHelpers include HttpBasicAuthHelpers @@ -12,12 +12,13 @@ RSpec.describe API::Ml::Mlflow do let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } let_it_be(:another_project) { build(:project).tap { |p| p.add_developer(developer) } } let_it_be(:experiment) do - create(:ml_experiments, user: project.creator, project: project) + create(:ml_experiments, :with_metadata, project: project) end let_it_be(:candidate) do create(:ml_candidates, - :with_metrics_and_params, user: experiment.user, start_time: 1234, experiment: experiment) + :with_metrics_and_params, :with_metadata, + user: experiment.user, start_time: 1234, experiment: experiment) end let_it_be(:tokens) do @@ -151,7 +152,17 @@ RSpec.describe API::Ml::Mlflow do 'experiment_id' => experiment_iid, 'name' => experiment.name, 'lifecycle_stage' => 'active', - 'artifact_location' => 'not_implemented' + 'artifact_location' => 'not_implemented', + 'tags' => [ + { + 'key' => experiment.metadata[0].name, + 'value' => experiment.metadata[0].value + }, + { + 'key' => experiment.metadata[1].name, + 'value' => experiment.metadata[1].value + } + ] } }) end @@ -187,7 +198,17 @@ RSpec.describe API::Ml::Mlflow do 'experiment_id' => experiment.iid.to_s, 'name' => experiment.name, 'lifecycle_stage' => 'active', - 'artifact_location' => 'not_implemented' + 'artifact_location' => 'not_implemented', + 'tags' => [ + { + 'key' => experiment.metadata[0].name, + 'value' => experiment.metadata[0].value + }, + { + 'key' => experiment.metadata[1].name, + 'value' => experiment.metadata[1].value + } + ] ] }) end @@ -220,7 +241,17 @@ RSpec.describe API::Ml::Mlflow do 'experiment_id' => experiment.iid.to_s, 'name' => experiment_name, 'lifecycle_stage' => 'active', - 'artifact_location' => 'not_implemented' + 'artifact_location' => 'not_implemented', + 'tags' => [ + { + 'key' => experiment.metadata[0].name, + 'value' => experiment.metadata[0].value + }, + { + 'key' => experiment.metadata[1].name, + 'value' => experiment.metadata[1].value + } + ] } }) end @@ -284,10 +315,44 @@ RSpec.describe API::Ml::Mlflow do end end + describe 'POST /projects/:id/ml/mlflow/api/2.0/mlflow/experiments/set-experiment-tag' do + let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/experiments/set-experiment-tag" } + let(:default_params) { { experiment_id: experiment.iid.to_s, key: 'some_key', value: 'value' } } + let(:params) { default_params } + let(:request) { post api(route), params: params, headers: headers } + + it 'logs the tag', :aggregate_failures do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_empty + expect(experiment.reload.metadata.map(&:name)).to include('some_key') + end + + describe 'Error Cases' do + context 'when tag was already set' do + let(:params) { default_params.merge(key: experiment.metadata[0].name) } + + it_behaves_like 'Bad Request' + end + + it_behaves_like 'shared error cases' + it_behaves_like 'Requires api scope' + it_behaves_like 'Bad Request on missing required', [:key, :value] + end + end + describe 'Runs' do describe 'POST /projects/:id/ml/mlflow/api/2.0/mlflow/runs/create' do let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/runs/create" } - let(:params) { { experiment_id: experiment.iid.to_s, start_time: Time.now.to_i } } + let(:params) do + { + experiment_id: experiment.iid.to_s, + start_time: Time.now.to_i, + tags: [ + { key: 'hello', value: 'world' } + ] + } + end + let(:request) { post api(route), params: params, headers: headers } it 'creates the run', :aggregate_failures do @@ -295,14 +360,18 @@ RSpec.describe API::Ml::Mlflow do 'experiment_id' => params[:experiment_id], 'user_id' => current_user.id.to_s, 'start_time' => params[:start_time], - 'status' => "RUNNING", - 'lifecycle_stage' => "active" + 'status' => 'RUNNING', + 'lifecycle_stage' => 'active' } expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('ml/run') expect(json_response['run']).to include('info' => hash_including(**expected_properties), - 'data' => { 'metrics' => [], 'params' => [] }) + 'data' => { + 'metrics' => [], + 'params' => [], + 'tags' => [{ 'key' => 'hello', 'value' => 'world' }] + }) end describe 'Error States' do @@ -355,6 +424,10 @@ RSpec.describe API::Ml::Mlflow do 'params' => [ { 'key' => candidate.params[0].name, 'value' => candidate.params[0].value }, { 'key' => candidate.params[1].name, 'value' => candidate.params[1].value } + ], + 'tags' => [ + { 'key' => 'metadata_1', 'value' => 'value1' }, + { 'key' => 'metadata_2', 'value' => 'value2' } ] }) end @@ -454,6 +527,31 @@ RSpec.describe API::Ml::Mlflow do end end + describe 'POST /projects/:id/ml/mlflow/api/2.0/mlflow/runs/set-tag' do + let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/runs/set-tag" } + let(:default_params) { { run_id: candidate.iid.to_s, key: 'some_key', value: 'value' } } + let(:request) { post api(route), params: params, headers: headers } + + it 'logs the tag', :aggregate_failures do + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_empty + expect(candidate.reload.metadata.map(&:name)).to include('some_key') + end + + describe 'Error Cases' do + context 'when tag was already logged' do + let(:params) { default_params.tap { |p| p[:key] = candidate.metadata[0].name } } + + it_behaves_like 'Bad Request' + end + + it_behaves_like 'shared error cases' + it_behaves_like 'Requires api scope' + it_behaves_like 'run_id param error cases' + it_behaves_like 'Bad Request on missing required', [:key, :value] + end + end + describe 'POST /projects/:id/ml/mlflow/api/2.0/mlflow/runs/log-batch' do let(:candidate2) do create(:ml_candidates, user: experiment.user, start_time: 1234, experiment: experiment) @@ -467,7 +565,8 @@ RSpec.describe API::Ml::Mlflow do { key: 'mae', value: 2.5, timestamp: 1552550804 }, { key: 'rmse', value: 2.7, timestamp: 1552550804 } ], - params: [{ key: 'model_class', value: 'LogisticRegression' }] + params: [{ key: 'model_class', value: 'LogisticRegression' }], + tags: [{ key: 'tag1', value: 'tag.value.1' }] } end @@ -477,6 +576,7 @@ RSpec.describe API::Ml::Mlflow do expect(response).to have_gitlab_http_status(:ok) expect(json_response).to be_empty expect(candidate2.params.size).to eq(1) + expect(candidate2.metadata.size).to eq(1) expect(candidate2.metrics.size).to eq(2) end @@ -493,6 +593,19 @@ RSpec.describe API::Ml::Mlflow do end end + context 'when tag was already logged' do + let(:params) do + default_params.tap { |p| p[:tags] = [{ key: 'tag1', value: 'a' }, { key: 'tag1', value: 'b' }] } + end + + it 'logs only 1', :aggregate_failures do + candidate.metadata.reload + + expect(response).to have_gitlab_http_status(:ok) + expect(candidate2.metadata.size).to eq(1) + end + end + describe 'Error Cases' do context 'when required metric key is missing' do let(:params) { default_params.tap { |p| p[:metrics] = [p[:metrics][0].delete(:key)] } } diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index ab39c29653f..30616964371 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Namespaces do +RSpec.describe API::Namespaces, feature_category: :subgroups do let_it_be(:admin) { create(:admin) } let_it_be(:user) { create(:user) } let_it_be(:group1) { create(:group, name: 'group.one') } diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 89abb28140a..c2d9db1e6fb 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Notes do +RSpec.describe API::Notes, feature_category: :team_planning do let!(:user) { create(:user) } let!(:project) { create(:project, :public) } let(:private_user) { create(:user) } @@ -203,6 +203,51 @@ RSpec.describe API::Notes do end end end + + context 'without notes widget' do + let(:request_body) { 'Hi!' } + let(:params) { { body: request_body } } + let(:request_path) { "/projects/#{ext_proj.id}/issues/#{ext_issue.iid}/notes" } + + before do + stub_const('WorkItems::Type::BASE_TYPES', { issue: { name: 'NoNotesWidget', enum_value: 0 } }) + stub_const('WorkItems::Type::WIDGETS_FOR_TYPE', { issue: [::WorkItems::Widgets::Description] }) + end + + it 'does not fetch notes' do + get api(request_path, private_user) + + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'does not fetch specific note' do + get api("#{request_path}/#{cross_reference_note.id}", private_user) + + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'does not create note' do + post api(request_path, private_user), params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'does not update note' do + put api("#{request_path}/#{cross_reference_note.id}", private_user), params: params + + expect(response).to have_gitlab_http_status(:not_found) + end + + it 'does not run quick actions' do + params[:body] = "/spend 1h" + + expect do + post api("#{request_path}/#{cross_reference_note.id}", private_user), params: params + end.to not_change { Note.system.count }.and(not_change { Note.where(system: false).count }) + + expect(response).to have_gitlab_http_status(:not_found) + end + end end end diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb index b5551c21738..2a80dc4bbe9 100644 --- a/spec/requests/api/notification_settings_spec.rb +++ b/spec/requests/api/notification_settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::NotificationSettings do +RSpec.describe API::NotificationSettings, feature_category: :team_planning 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_instance_packages_spec.rb b/spec/requests/api/npm_instance_packages_spec.rb index 698885ddcf4..dcd2e4ae677 100644 --- a/spec/requests/api/npm_instance_packages_spec.rb +++ b/spec/requests/api/npm_instance_packages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::NpmInstancePackages do +RSpec.describe API::NpmInstancePackages, feature_category: :package_registry do # We need to create a subgroup with the same name as the hosting group. # It has to be created first to exhibit this bug: https://gitlab.com/gitlab-org/gitlab/-/issues/321958 let_it_be(:another_namespace) { create(:group, :public) } @@ -33,4 +33,16 @@ RSpec.describe API::NpmInstancePackages do let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") } end end + + describe 'POST /api/v4/packages/npm/-/npm/v1/security/advisories/bulk' do + it_behaves_like 'handling audit request', path: 'advisories/bulk', scope: :instance do + let(:url) { api('/packages/npm/-/npm/v1/security/advisories/bulk') } + end + end + + describe 'POST /api/v4/packages/npm/-/npm/v1/security/audits/quick' do + it_behaves_like 'handling audit request', path: 'audits/quick', scope: :instance do + let(:url) { api('/packages/npm/-/npm/v1/security/audits/quick') } + end + end end diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb index 373327787a2..c62c0849776 100644 --- a/spec/requests/api/npm_project_packages_spec.rb +++ b/spec/requests/api/npm_project_packages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::NpmProjectPackages do +RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do include_context 'npm api setup' shared_examples 'accept get request on private project with access to package registry for everyone' do @@ -42,8 +42,19 @@ RSpec.describe API::NpmProjectPackages do end end + describe 'POST /api/v4/projects/:id/packages/npm/-/npm/v1/security/advisories/bulk' do + it_behaves_like 'handling audit request', path: 'advisories/bulk', scope: :project do + let(:url) { api("/projects/#{project.id}/packages/npm/-/npm/v1/security/advisories/bulk") } + end + end + + describe 'POST /api/v4/projects/:id/packages/npm/-/npm/v1/security/audits/quick' do + it_behaves_like 'handling audit request', path: 'audits/quick', scope: :project do + let(:url) { api("/projects/#{project.id}/packages/npm/-/npm/v1/security/audits/quick") } + end + end + describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } let(:package_file) { package.package_files.first } let(:headers) { {} } @@ -203,7 +214,7 @@ RSpec.describe API::NpmProjectPackages do let_it_be(:version) { '1.2.3' } let(:params) { upload_params(package_name: package_name, package_version: version) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_npm_user' } } shared_examples 'handling upload with different authentications' do context 'with access token' do diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb index c1375288809..9de612f7bc7 100644 --- a/spec/requests/api/nuget_group_packages_spec.rb +++ b/spec/requests/api/nuget_group_packages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::NugetGroupPackages do +RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do include_context 'nuget api setup' using RSpec::Parameterized::TableSyntax @@ -12,6 +12,7 @@ RSpec.describe API::NugetGroupPackages do let_it_be(:deploy_token) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) } let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: group) } + let(:snowplow_gitlab_standard_context) { { namespace: project.group, property: 'i_package_nuget_user' } } let(:target_type) { 'groups' } shared_examples 'handling all endpoints' do @@ -46,7 +47,6 @@ RSpec.describe API::NugetGroupPackages do let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: subgroup) } let(:target) { subgroup } - let(:snowplow_gitlab_standard_context) { { namespace: subgroup } } it_behaves_like 'handling all endpoints' @@ -58,7 +58,7 @@ RSpec.describe API::NugetGroupPackages do context 'a group' do let(:target) { group } - let(:snowplow_gitlab_standard_context) { { namespace: group } } + let(:snowplow_gitlab_standard_context) { { namespace: target, property: 'i_package_nuget_user' } } it_behaves_like 'handling all endpoints' diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb index f608f296295..1e0d35ad451 100644 --- a/spec/requests/api/nuget_project_packages_spec.rb +++ b/spec/requests/api/nuget_project_packages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::NugetProjectPackages do +RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do include_context 'nuget api setup' using RSpec::Parameterized::TableSyntax @@ -9,38 +9,62 @@ RSpec.describe API::NugetProjectPackages do let_it_be_with_reload(:project) { create(:project, :public) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } + let_it_be(:package_name) { 'Dummy.Package' } let(:target) { project } let(:target_type) { 'projects' } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_nuget_user' } } - describe 'GET /api/v4/projects/:id/packages/nuget' do - it_behaves_like 'handling nuget service requests' do - let(:url) { "/projects/#{target.id}/packages/nuget/index.json" } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + shared_examples 'accept get request on private project with access to package registry for everyone' do + subject { get api(url) } + + before do + update_visibility_to(Gitlab::VisibilityLevel::PRIVATE) + project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC) end + + it_behaves_like 'returning response status', :ok + end + + describe 'GET /api/v4/projects/:id/packages/nuget' do + let(:url) { "/projects/#{target.id}/packages/nuget/index.json" } + + it_behaves_like 'handling nuget service requests' + + it_behaves_like 'accept get request on private project with access to package registry for everyone' end describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do - it_behaves_like 'handling nuget metadata requests with package name' do - let(:url) { "/projects/#{target.id}/packages/nuget/metadata/#{package_name}/index.json" } + let(:url) { "/projects/#{target.id}/packages/nuget/metadata/#{package_name}/index.json" } + + it_behaves_like 'handling nuget metadata requests with package name' + + it_behaves_like 'accept get request on private project with access to package registry for everyone' do + let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) } end end describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do - it_behaves_like 'handling nuget metadata requests with package name and package version' do - let(:url) { "/projects/#{target.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" } + let(:url) { "/projects/#{target.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" } + + it_behaves_like 'handling nuget metadata requests with package name and package version' + + it_behaves_like 'accept get request on private project with access to package registry for everyone' do + let_it_be(:package) { create(:nuget_package, :with_metadatum, name: package_name, project: project) } end end describe 'GET /api/v4/projects/:id/packages/nuget/query' do - it_behaves_like 'handling nuget search requests' do - let(:url) { "/projects/#{target.id}/packages/nuget/query?#{query_parameters.to_query}" } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + let(:url) { "/projects/#{target.id}/packages/nuget/query?#{query_parameters.to_query}" } + + it_behaves_like 'handling nuget search requests' + + it_behaves_like 'accept get request on private project with access to package registry for everyone' do + let_it_be(:query_parameters) { { q: 'query', take: 5, skip: 0, prerelease: true } } 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/#{target.id}/packages/nuget/download/#{package_name}/index.json" } @@ -88,10 +112,11 @@ RSpec.describe API::NugetProjectPackages do it_behaves_like 'rejects nuget access with unknown target id' it_behaves_like 'rejects nuget access with invalid target id' + + it_behaves_like 'accept get request on private project with access to package registry for everyone' 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, :with_symbol_package, project: project, name: package_name) } let(:format) { 'nupkg' } @@ -124,7 +149,6 @@ RSpec.describe API::NugetProjectPackages do with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } subject { get api(url), headers: headers } @@ -134,6 +158,8 @@ RSpec.describe API::NugetProjectPackages do it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] end + + it_behaves_like 'accept get request on private project with access to package registry for everyone' end it_behaves_like 'deploy token for package GET requests' do diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb index f07dcfcccd6..b29f1e9e661 100644 --- a/spec/requests/api/oauth_tokens_spec.rb +++ b/spec/requests/api/oauth_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'OAuth tokens' do +RSpec.describe 'OAuth tokens', feature_category: :authentication_and_authorization do include HttpBasicAuthHelpers context 'Resource Owner Password Credentials' do @@ -85,8 +85,6 @@ RSpec.describe 'OAuth tokens' do context 'with invalid credentials' do it 'does not create an access token' do - pending 'Enable this example after https://github.com/doorkeeper-gem/doorkeeper/pull/1488 is merged and released' - user = create(:user) request_oauth_token(user, basic_auth_header(client.uid, 'invalid secret')) diff --git a/spec/requests/api/package_files_spec.rb b/spec/requests/api/package_files_spec.rb index 01c7ef1476f..f47dca387ef 100644 --- a/spec/requests/api/package_files_spec.rb +++ b/spec/requests/api/package_files_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::PackageFiles do +RSpec.describe API::PackageFiles, feature_category: :package_registry do let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:package) { create(:maven_package, project: project) } diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb index 4ac47f17b7e..fdc25ecdcd3 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' -RSpec.describe "Internal Project Pages Access" do +RSpec.describe "Internal Project Pages Access", feature_category: :pages 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 4a94bf90205..c426f2a433c 100644 --- a/spec/requests/api/pages/pages_spec.rb +++ b/spec/requests/api/pages/pages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Pages do +RSpec.describe API::Pages, feature_category: :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 c1c0e406508..5cc1b8f9a69 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' -RSpec.describe "Private Project Pages Access" do +RSpec.describe "Private Project Pages Access", feature_category: :pages 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 c45b3a4c55e..1137f91f4b0 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' -RSpec.describe "Public Project Pages Access" do +RSpec.describe "Public Project Pages Access", feature_category: :pages 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 8ef4e899193..65fcf9e006a 100644 --- a/spec/requests/api/pages_domains_spec.rb +++ b/spec/requests/api/pages_domains_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::PagesDomains do +RSpec.describe API::PagesDomains, feature_category: :pages 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/performance_bar_spec.rb b/spec/requests/api/performance_bar_spec.rb index a4dbb3d17b8..9fbe34914c5 100644 --- a/spec/requests/api/performance_bar_spec.rb +++ b/spec/requests/api/performance_bar_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe 'Performance Bar for API requests', :request_store, :clean_gitlab_redis_cache do +RSpec.describe 'Performance Bar for API requests', :request_store, :clean_gitlab_redis_cache, +feature_category: :metrics do context 'with user that has access to the performance bar' do let_it_be(:admin) { create(:admin) } diff --git a/spec/requests/api/personal_access_tokens/self_information_spec.rb b/spec/requests/api/personal_access_tokens/self_information_spec.rb index bdfac3ed14f..4a3c0ad8904 100644 --- a/spec/requests/api/personal_access_tokens/self_information_spec.rb +++ b/spec/requests/api/personal_access_tokens/self_information_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::PersonalAccessTokens::SelfInformation do +RSpec.describe API::PersonalAccessTokens::SelfInformation, feature_category: :authentication_and_authorization do let(:path) { '/personal_access_tokens/self' } let(:token) { create(:personal_access_token, user: current_user) } diff --git a/spec/requests/api/personal_access_tokens_spec.rb b/spec/requests/api/personal_access_tokens_spec.rb index 1fa2ad6ebfa..32adc7ebd61 100644 --- a/spec/requests/api/personal_access_tokens_spec.rb +++ b/spec/requests/api/personal_access_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::PersonalAccessTokens do +RSpec.describe API::PersonalAccessTokens, feature_category: :authentication_and_authorization do let_it_be(:path) { '/personal_access_tokens' } describe 'GET /personal_access_tokens' do diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml index 2ff4cd72f1e..cc399d25429 100644 --- a/spec/requests/api/project_attributes.yml +++ b/spec/requests/api/project_attributes.yml @@ -43,6 +43,7 @@ itself: # project - storage_version - topic_list - updated_at + - mirror_branch_regex remapped_attributes: avatar: avatar_url build_allow_git_fetch: build_git_strategy @@ -124,10 +125,6 @@ project_feature: - created_at - metrics_dashboard_access_level - package_registry_access_level - - monitor_access_level - - infrastructure_access_level - - feature_flags_access_level - - environments_access_level - project_id - updated_at computed_attributes: @@ -163,6 +160,7 @@ project_setting: - suggested_reviewers_enabled - jitsu_key - mirror_branch_regex + - allow_pipeline_trigger_approve_deployment build_service_desk_setting: # service_desk_setting unexposed_attributes: diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 4c7da78f0d4..895192252da 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectClusters do +RSpec.describe API::ProjectClusters, feature_category: :kubernetes_management do include KubernetesHelpers let_it_be(:maintainer_user) { create(:user) } diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb index 52ec06d76a9..a2e1a1c1721 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' -RSpec.describe API::ProjectContainerRepositories do +RSpec.describe API::ProjectContainerRepositories, feature_category: :package_registry do include ExclusiveLeaseHelpers let_it_be(:project) { create(:project, :private) } @@ -33,7 +33,10 @@ RSpec.describe API::ProjectContainerRepositories do let(:method) { :get } let(:params) { {} } - let(:snowplow_gitlab_standard_context) { { user: api_user, project: project, namespace: project.namespace } } + let(:snowplow_gitlab_standard_context) do + { user: api_user, project: project, namespace: project.namespace, + property: 'i_package_container_user' } + end before_all do project.add_maintainer(maintainer) @@ -144,20 +147,6 @@ RSpec.describe API::ProjectContainerRepositories do expect(response).to have_gitlab_http_status(:accepted) end - - context 'with container_registry_delete_repository_with_cron_worker disabled' do - before do - stub_feature_flags(container_registry_delete_repository_with_cron_worker: false) - end - - it 'schedules removal of repository' do - expect(DeleteContainerRepositoryWorker).to receive(:perform_async) - .with(maintainer.id, root_repository.id) - expect { subject }.to change { root_repository.reload.status }.from(nil).to('delete_scheduled') - - expect(response).to have_gitlab_http_status(:accepted) - end - end end end end @@ -414,6 +403,9 @@ RSpec.describe API::ProjectContainerRepositories do context 'for developer', :snowplow do let(:api_user) { developer } + let(:service_ping_context) do + [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: 'i_package_container_user').to_h] + end context 'when there are multiple tags' do before do @@ -427,7 +419,10 @@ RSpec.describe API::ProjectContainerRepositories do subject expect(response).to have_gitlab_http_status(:ok) - expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project, user: api_user, namespace: project.namespace) + expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project, + user: api_user, namespace: project.namespace.reload, + label: 'redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly', + property: 'i_package_container_user', context: service_ping_context) end end @@ -443,7 +438,10 @@ RSpec.describe API::ProjectContainerRepositories do subject expect(response).to have_gitlab_http_status(:ok) - expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project, user: api_user, namespace: project.namespace) + expect_snowplow_event(category: described_class.name, action: 'delete_tag', project: project, + user: api_user, namespace: project.namespace.reload, + label: 'redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly', + property: 'i_package_container_user', context: service_ping_context) end end end diff --git a/spec/requests/api/project_debian_distributions_spec.rb b/spec/requests/api/project_debian_distributions_spec.rb index 2b993f24046..9807f177c5d 100644 --- a/spec/requests/api/project_debian_distributions_spec.rb +++ b/spec/requests/api/project_debian_distributions_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::ProjectDebianDistributions do +RSpec.describe API::ProjectDebianDistributions, feature_category: :package_registry do include HttpBasicAuthHelpers include WorkhorseHelpers @@ -23,13 +23,13 @@ RSpec.describe API::ProjectDebianDistributions do describe 'GET projects/:id/debian_distributions' do let(:url) { "/projects/#{container.id}/debian_distributions" } - it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^\[{.*"codename":"existing-codename\",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/ + it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^\[{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/ end describe 'GET projects/:id/debian_distributions/:codename' do let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}" } - it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^{.*"codename":"existing-codename\",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/ + it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/ end describe 'GET projects/:id/debian_distributions/:codename/key.asc' do @@ -56,7 +56,7 @@ RSpec.describe API::ProjectDebianDistributions do let(:method) { :delete } let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}" } - it_behaves_like 'Debian distributions maintainer write endpoint', 'DELETE', :success, /^{\"message\":\"202 Accepted\"}$/ + it_behaves_like 'Debian distributions maintainer write endpoint', 'DELETE', :success, /^{"message":"202 Accepted"}$/ context 'when destroy fails' do before do diff --git a/spec/requests/api/project_events_spec.rb b/spec/requests/api/project_events_spec.rb index f3e592f9796..69d8eb76cf3 100644 --- a/spec/requests/api/project_events_spec.rb +++ b/spec/requests/api/project_events_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectEvents do +RSpec.describe API::ProjectEvents, feature_category: :users 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 d74fd82ca09..fdd76c63069 100644 --- a/spec/requests/api/project_export_spec.rb +++ b/spec/requests/api/project_export_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do +RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache, feature_category: :importers do let_it_be(:project) { create(:project) } let_it_be(:project_none) { create(:project) } let_it_be(:project_started) { create(:project) } diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 2d925620a91..8e5e9d847ea 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectHooks, 'ProjectHooks' do +RSpec.describe API::ProjectHooks, 'ProjectHooks', feature_category: :integrations do let_it_be(:user) { create(:user) } let_it_be(:user3) { create(:user) } let_it_be(: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 05fe55b06a1..027c61bb9e1 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectImport, :aggregate_failures do +RSpec.describe API::ProjectImport, :aggregate_failures, feature_category: :importers do include WorkhorseHelpers include AfterNextHelpers @@ -44,7 +44,7 @@ RSpec.describe API::ProjectImport, :aggregate_failures do it_behaves_like 'requires authentication' - it 'executes a limited number of queries' do + it 'executes a limited number of queries', :use_clean_rails_redis_caching do control_count = ActiveRecord::QueryRecorder.new { subject }.count expect(control_count).to be <= 111 @@ -126,13 +126,31 @@ RSpec.describe API::ProjectImport, :aggregate_failures do end end - it 'schedules an import at the user namespace level' do - stub_import(user.namespace) - params[:path] = 'test-import2' + context 'when namespace not set' do + it 'schedules an import at the user namespace level' do + stub_import(user.namespace) + params[:path] = 'test-import2' - subject + subject - expect(response).to have_gitlab_http_status(:created) + expect(response).to have_gitlab_http_status(:created) + end + + context 'when current user is a bot user' do + let(:user) { create(:user, :project_bot) } + + it 'does not schedule an import' do + expect_any_instance_of(ProjectImportState).not_to receive(:schedule) + + params[:namespace] = nil + params[:path] = 'test-import3' + + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq("Namespace is not valid") + end + end end it 'does not schedule an import for a namespace that does not exist' do @@ -161,6 +179,20 @@ RSpec.describe API::ProjectImport, :aggregate_failures do expect(json_response['message']).to eq('404 Namespace Not Found') end + context 'when passed in namespace is a bot user namespace' do + let(:user) { create(:user, :project_bot) } + + it 'does not schedule an import' do + expect_any_instance_of(ProjectImportState).not_to receive(:schedule) + params[:namespace] = user.namespace.full_path + + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['message']).to eq("Namespace is not valid") + end + end + context 'if user uploads no valid file' do let(:file) { 'README.md' } diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index 8294ca143d3..9d722e4a445 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectMilestones do +RSpec.describe API::ProjectMilestones, feature_category: :team_planning do let_it_be(:user) { create(:user) } let_it_be_with_reload(:project) { create(:project, namespace: user.namespace) } let_it_be(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb index 00d295b3490..d3adef85f8d 100644 --- a/spec/requests/api/project_packages_spec.rb +++ b/spec/requests/api/project_packages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectPackages do +RSpec.describe API::ProjectPackages, feature_category: :package_registry do let_it_be(:project) { create(:project, :public) } let(:user) { create(:user) } @@ -350,6 +350,16 @@ RSpec.describe API::ProjectPackages do end end end + + context 'when package has no default status' do + let!(:package1) { create(:npm_package, :error, project: project) } + + it 'returns 404' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end end describe 'DELETE /projects/:id/packages/:package_id' do diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb index 5b272121233..96ed3042d00 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' -RSpec.describe API::ProjectRepositoryStorageMoves do +RSpec.describe API::ProjectRepositoryStorageMoves, feature_category: :gitaly do it_behaves_like 'repository_storage_moves API', 'projects' do let_it_be(:container) { create(:project, :repository) } let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, container: container) } diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb index bf78ff56206..5d3c596e605 100644 --- a/spec/requests/api/project_snapshots_spec.rb +++ b/spec/requests/api/project_snapshots_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectSnapshots do +RSpec.describe API::ProjectSnapshots, feature_category: :source_code_management do include WorkhorseHelpers let(:project) { create(:project) } @@ -21,7 +21,7 @@ RSpec.describe API::ProjectSnapshots do expect(type).to eq('git-snapshot') expect(params).to eq( 'GitalyServer' => { - 'features' => { 'gitaly-feature-foobar' => 'true' }, + 'call_metadata' => { 'gitaly-feature-foobar' => 'true' }, 'address' => Gitlab::GitalyClient.address(repository.project.repository_storage), 'token' => Gitlab::GitalyClient.token(repository.project.repository_storage) }, diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 1d255f7c1d8..568486deb7f 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectSnippets do +RSpec.describe API::ProjectSnippets, feature_category: :source_code_management do include SnippetHelpers let_it_be(:project) { create(:project, :public) } diff --git a/spec/requests/api/project_statistics_spec.rb b/spec/requests/api/project_statistics_spec.rb index d314af0746a..39ead8cc573 100644 --- a/spec/requests/api/project_statistics_spec.rb +++ b/spec/requests/api/project_statistics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectStatistics do +RSpec.describe API::ProjectStatistics, feature_category: :source_code_management do let_it_be(:reporter) { 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 87d70a87f42..38d6a05a104 100644 --- a/spec/requests/api/project_templates_spec.rb +++ b/spec/requests/api/project_templates_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProjectTemplates do +RSpec.describe API::ProjectTemplates, feature_category: :source_code_management do let_it_be(:public_project) { create(:project, :public, :repository, create_templates: :merge_request, path: 'path.with.dot') } let_it_be(:private_project) { create(:project, :private, :repository, create_templates: :issue) } let_it_be(:developer) { create(:user) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 3831e8e1dfe..6e8168c0ee1 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.shared_examples 'languages and percentages JSON response' do +RSpec.shared_examples 'languages and percentages JSON response', feature_category: :projects do let(:expected_languages) { project.repository.languages.to_h { |language| language.values_at(:label, :value) } } before do @@ -231,14 +231,16 @@ RSpec.describe API::Projects do include_examples 'includes container_registry_access_level' end - it 'includes releases_access_level', :aggregate_failures do - project.project_feature.update!(releases_access_level: ProjectFeature::DISABLED) - + it 'includes various project feature fields', :aggregate_failures do get api('/projects', user) project_response = json_response.find { |p| p['id'] == project.id } expect(response).to have_gitlab_http_status(:ok) - expect(project_response['releases_access_level']).to eq('disabled') + expect(project_response['releases_access_level']).to eq('enabled') + expect(project_response['environments_access_level']).to eq('enabled') + expect(project_response['feature_flags_access_level']).to eq('enabled') + expect(project_response['infrastructure_access_level']).to eq('enabled') + expect(project_response['monitor_access_level']).to eq('enabled') end context 'when some projects are in a group' do @@ -1192,6 +1194,10 @@ RSpec.describe API::Projects do attrs[:container_registry_access_level] = 'private' attrs[:security_and_compliance_access_level] = 'private' attrs[:releases_access_level] = 'disabled' + attrs[:environments_access_level] = 'disabled' + attrs[:feature_flags_access_level] = 'disabled' + attrs[:infrastructure_access_level] = 'disabled' + attrs[:monitor_access_level] = 'disabled' end post api('/projects', user), params: project @@ -1201,7 +1207,8 @@ RSpec.describe API::Projects do project.each_pair do |k, v| next if %i[ has_external_issue_tracker has_external_wiki issues_enabled merge_requests_enabled wiki_enabled storage_version - container_registry_access_level releases_access_level + container_registry_access_level releases_access_level environments_access_level feature_flags_access_level + infrastructure_access_level monitor_access_level ].include?(k) expect(json_response[k.to_s]).to eq(v) @@ -1217,6 +1224,10 @@ RSpec.describe API::Projects do expect(project.project_feature.container_registry_access_level).to eq(ProjectFeature::PRIVATE) expect(project.project_feature.security_and_compliance_access_level).to eq(ProjectFeature::PRIVATE) expect(project.project_feature.releases_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.environments_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.feature_flags_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.infrastructure_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.monitor_access_level).to eq(ProjectFeature::DISABLED) end it 'assigns container_registry_enabled to project', :aggregate_failures do @@ -2356,6 +2367,10 @@ RSpec.describe API::Projects do expect(json_response['operations_access_level']).to be_present expect(json_response['security_and_compliance_access_level']).to be_present expect(json_response['releases_access_level']).to be_present + expect(json_response['environments_access_level']).to be_present + expect(json_response['feature_flags_access_level']).to be_present + expect(json_response['infrastructure_access_level']).to be_present + expect(json_response['monitor_access_level']).to be_present end it 'exposes all necessary attributes' do @@ -2426,6 +2441,10 @@ RSpec.describe API::Projects do expect(json_response['operations_access_level']).to be_present expect(json_response['security_and_compliance_access_level']).to be_present expect(json_response['releases_access_level']).to be_present + expect(json_response['environments_access_level']).to be_present + expect(json_response['feature_flags_access_level']).to be_present + expect(json_response['infrastructure_access_level']).to be_present + expect(json_response['monitor_access_level']).to be_present expect(json_response).to have_key('emails_disabled') expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions) expect(json_response['remove_source_branch_after_merge']).to be_truthy @@ -3410,12 +3429,14 @@ RSpec.describe API::Projects do expect(Project.find_by(path: project[:path]).analytics_access_level).to eq(ProjectFeature::PRIVATE) end - it 'sets releases_access_level', :aggregate_failures do - put api("/projects/#{project.id}", user), params: { releases_access_level: 'private' } + %i(releases_access_level environments_access_level feature_flags_access_level infrastructure_access_level monitor_access_level).each do |field| + it "sets #{field}", :aggregate_failures do + put api("/projects/#{project.id}", user), params: { field => 'private' } - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['releases_access_level']).to eq('private') - expect(Project.find_by(path: project[:path]).releases_access_level).to eq(ProjectFeature::PRIVATE) + expect(response).to have_gitlab_http_status(:ok) + expect(json_response[field.to_s]).to eq('private') + expect(Project.find_by(path: project[:path]).public_send(field)).to eq(ProjectFeature::PRIVATE) + end end it 'returns 400 when nothing sent' do @@ -4687,6 +4708,7 @@ RSpec.describe API::Projects do end end end + describe 'PUT /projects/:id/transfer' do context 'when authenticated as owner' do let(:group) { create :group } diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb index b46859a0e70..8e8a25a8dc2 100644 --- a/spec/requests/api/protected_branches_spec.rb +++ b/spec/requests/api/protected_branches_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProtectedBranches do +RSpec.describe API::ProtectedBranches, feature_category: :source_code_management do let_it_be_with_reload(:project) { create(:project, :repository) } let_it_be(:maintainer) { create(:user) } let_it_be(:guest) { create(:user) } diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb index f1db39ac204..5b128d4ec9e 100644 --- a/spec/requests/api/protected_tags_spec.rb +++ b/spec/requests/api/protected_tags_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::ProtectedTags do +RSpec.describe API::ProtectedTags, feature_category: :source_code_management do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :repository) } let_it_be(: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 index 12091158a02..59d93cd48e3 100644 --- a/spec/requests/api/pypi_packages_spec.rb +++ b/spec/requests/api/pypi_packages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::PypiPackages do +RSpec.describe API::PypiPackages, feature_category: :package_registry do include WorkhorseHelpers include PackagesManagerApiSpecHelpers include HttpBasicAuthHelpers @@ -14,6 +14,7 @@ RSpec.describe API::PypiPackages do let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:job) { create(:ci_build, :running, user: user, project: project) } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } } let(:headers) { {} } @@ -25,7 +26,7 @@ RSpec.describe API::PypiPackages do describe 'GET /api/v4/groups/:id/-/packages/pypi/simple' do let(:url) { "/groups/#{group.id}/-/packages/pypi/simple" } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } } it_behaves_like 'pypi simple index API endpoint' it_behaves_like 'rejects PyPI access with unknown group id' @@ -63,7 +64,7 @@ RSpec.describe API::PypiPackages do describe 'GET /api/v4/projects/:id/packages/pypi/simple' do let(:package_name) { package.name } let(:url) { "/projects/#{project.id}/packages/pypi/simple" } - let(:snowplow_gitlab_standard_context) { { project: nil, namespace: group } } + let(:snowplow_gitlab_standard_context) { { project: nil, namespace: group, property: 'i_package_pypi_user' } } it_behaves_like 'pypi simple index API endpoint' it_behaves_like 'rejects PyPI access with unknown project id' @@ -81,13 +82,13 @@ RSpec.describe API::PypiPackages do context 'simple package API endpoint' do let_it_be(:package) { create(:pypi_package, project: project) } + let(:snowplow_gitlab_standard_context) { { project: nil, namespace: group, property: 'i_package_pypi_user' } } subject { get api(url), headers: headers } describe 'GET /api/v4/groups/:id/-/packages/pypi/simple/:package_name' do let(:package_name) { package.name } let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package_name}" } - let(:snowplow_gitlab_standard_context) { { project: nil, namespace: group } } it_behaves_like 'pypi simple API endpoint' it_behaves_like 'rejects PyPI access with unknown group id' @@ -125,7 +126,7 @@ RSpec.describe API::PypiPackages do describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do let(:package_name) { package.name } let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package_name}" } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_pypi_user' } } it_behaves_like 'pypi simple API endpoint' it_behaves_like 'rejects PyPI access with unknown project id' @@ -202,7 +203,7 @@ RSpec.describe API::PypiPackages do let(:base_params) { { requires_python: requires_python, version: '1.0.0', name: 'sample-project', sha256_digest: '1' * 64, md5_digest: '1' * 32 } } let(:params) { base_params.merge(content: temp_file(file_name)) } let(:send_rewritten_field) { true } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_pypi_user' } } subject do workhorse_finalize( @@ -366,7 +367,6 @@ RSpec.describe API::PypiPackages do describe 'GET /api/v4/groups/:id/-/packages/pypi/files/:sha256/*file_identifier' do let(:url) { "/groups/#{group.id}/-/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" } - let(:snowplow_gitlab_standard_context) { {} } it_behaves_like 'pypi file download endpoint' it_behaves_like 'rejects PyPI access with unknown group id' @@ -375,7 +375,6 @@ RSpec.describe API::PypiPackages do describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } it_behaves_like 'pypi file download endpoint' it_behaves_like 'rejects PyPI access with unknown project id' diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb index 38166c5ce97..6036960c43c 100644 --- a/spec/requests/api/release/links_spec.rb +++ b/spec/requests/api/release/links_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Release::Links do +RSpec.describe API::Release::Links, feature_category: :release_orchestration do let(:project) { create(:project, :repository, :private) } let(:maintainer) { create(:user) } let(:developer) { create(:user) } diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index 754b77af60e..a1aff9a6b1c 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Releases do +RSpec.describe API::Releases, feature_category: :release_orchestration 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 338647224e0..3da1760e319 100644 --- a/spec/requests/api/remote_mirrors_spec.rb +++ b/spec/requests/api/remote_mirrors_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::RemoteMirrors do +RSpec.describe API::RemoteMirrors, feature_category: :source_code_management 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) } } @@ -90,7 +90,9 @@ RSpec.describe API::RemoteMirrors do } expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']['url']).to eq(["is blocked: Only allowed schemes are ssh, git, http, https"]) + expect(json_response['message']['url']).to match_array( + ["is blocked: Only allowed schemes are http, https, ssh, git"] + ) end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 3c22f918af5..393ada1da4f 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' -RSpec.describe API::Repositories do +RSpec.describe API::Repositories, feature_category: :source_code_management do include RepoHelpers include WorkhorseHelpers include ProjectForksHelper @@ -300,7 +300,7 @@ RSpec.describe API::Repositories do type, params = workhorse_send_data expect(type).to eq('git-archive') - expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\.tar.gz/) + expect(params['ArchivePath']).to match(/#{project.path}-[^.]+\.tar.gz/) expect(response.parsed_body).to be_empty end @@ -312,7 +312,7 @@ RSpec.describe API::Repositories do type, params = workhorse_send_data expect(type).to eq('git-archive') - expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\.zip/) + expect(params['ArchivePath']).to match(/#{project.path}-[^.]+\.zip/) end it 'returns the repository archive archive.tar.bz2' do @@ -323,7 +323,7 @@ RSpec.describe API::Repositories do type, params = workhorse_send_data expect(type).to eq('git-archive') - expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\.tar.bz2/) + expect(params['ArchivePath']).to match(/#{project.path}-[^.]+\.tar.bz2/) end context 'when sha does not exist' do @@ -342,7 +342,7 @@ RSpec.describe API::Repositories do type, params = workhorse_send_data expect(type).to eq('git-archive') - expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\-#{path}\.tar.gz/) + expect(params['ArchivePath']).to match(/#{project.path}-[^.]+-#{path}\.tar.gz/) end it 'rate limits user when thresholds hit' do diff --git a/spec/requests/api/resource_access_tokens_spec.rb b/spec/requests/api/resource_access_tokens_spec.rb index 24efac3128d..6a89e9a56df 100644 --- a/spec/requests/api/resource_access_tokens_spec.rb +++ b/spec/requests/api/resource_access_tokens_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe API::ResourceAccessTokens do +RSpec.describe API::ResourceAccessTokens, feature_category: :authentication_and_authorization do let_it_be(:user) { create(:user) } let_it_be(:user_non_priviledged) { create(:user) } diff --git a/spec/requests/api/resource_label_events_spec.rb b/spec/requests/api/resource_label_events_spec.rb index a4a70d89812..1adffea17b7 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' -RSpec.describe API::ResourceLabelEvents do +RSpec.describe API::ResourceLabelEvents, feature_category: :team_planning 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 5c81c2180d7..fe991533c85 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' -RSpec.describe API::ResourceMilestoneEvents do +RSpec.describe API::ResourceMilestoneEvents, feature_category: :team_planning 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/rpm_project_packages_spec.rb b/spec/requests/api/rpm_project_packages_spec.rb index 68511795c94..515970f86a1 100644 --- a/spec/requests/api/rpm_project_packages_spec.rb +++ b/spec/requests/api/rpm_project_packages_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe API::RpmProjectPackages do +RSpec.describe API::RpmProjectPackages, feature_category: :package_registry do include HttpBasicAuthHelpers include WorkhorseHelpers @@ -136,7 +136,7 @@ RSpec.describe API::RpmProjectPackages do end describe 'GET /api/v4/projects/:id/packages/rpm/:package_file_id/:filename' do - let(:snowplow_gitlab_standard_context) { { project: project, namespace: group } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: group, property: 'i_package_rpm_user' } } let(:url) { "/projects/#{project.id}/packages/rpm/#{package_file_id}/#{package_name}" } subject { get api(url), headers: headers } @@ -148,7 +148,10 @@ RSpec.describe API::RpmProjectPackages do end describe 'POST /api/v4/projects/:project_id/packages/rpm' do - let(:snowplow_gitlab_standard_context) { { project: project, namespace: group, user: user } } + let(:snowplow_gitlab_standard_context) do + { project: project, namespace: group, user: user, property: 'i_package_rpm_user' } + end + let(:url) { "/projects/#{project.id}/packages/rpm" } let(:file_upload) { fixture_file_upload('spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm') } @@ -213,6 +216,19 @@ RSpec.describe API::RpmProjectPackages do expect(response.body).to match(/File is too large/) end end + + context 'when filelists.xml file size too large' do + before do + create(:rpm_repository_file, :filelists, size: 21.megabytes, project: project) + end + + it 'returns an error' do + upload_file(params: { file: file_upload }, request_headers: headers) + + expect(response).to have_gitlab_http_status(:bad_request) + expect(response.body).to match(/Repository packages limit exceeded/) + end + end end def upload_file(params: {}, request_headers: headers) diff --git a/spec/requests/api/rubygem_packages_spec.rb b/spec/requests/api/rubygem_packages_spec.rb index a7d461781b8..6f048fa57a8 100644 --- a/spec/requests/api/rubygem_packages_spec.rb +++ b/spec/requests/api/rubygem_packages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::RubygemPackages do +RSpec.describe API::RubygemPackages, feature_category: :package_registry do include PackagesManagerApiSpecHelpers include WorkhorseHelpers using RSpec::Parameterized::TableSyntax @@ -15,7 +15,7 @@ RSpec.describe API::RubygemPackages do let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) } let_it_be(:headers) { {} } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user, property: 'i_package_rubygems_user' } } let(:tokens) do { @@ -164,7 +164,7 @@ RSpec.describe API::RubygemPackages do with_them do let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' } let(:headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, property: 'i_package_rubygems_user' } } before do project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s)) @@ -323,7 +323,7 @@ RSpec.describe API::RubygemPackages do let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' } let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } } let(:headers) { user_headers.merge(workhorse_headers) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: snowplow_user } } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: snowplow_user, property: 'i_package_rubygems_user' } } let(:snowplow_user) do case token_type when :deploy_token diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index 60acf6b71dd..430d3b7d187 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Search do +RSpec.describe API::Search, feature_category: :global_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/settings_spec.rb b/spec/requests/api/settings_spec.rb index 3a9b2d02af5..e93ef52ef03 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do +RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, feature_category: :not_owned do let(:user) { create(:user) } let_it_be(:admin) { create(:admin) } @@ -25,6 +25,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do expect(json_response['secret_detection_token_revocation_url']).to be_nil expect(json_response['secret_detection_revocation_token_types_url']).to be_nil expect(json_response['sourcegraph_public_only']).to be_truthy + expect(json_response['default_preferred_language']).to be_a String expect(json_response['default_project_visibility']).to be_a String expect(json_response['default_snippet_visibility']).to be_a String expect(json_response['default_group_visibility']).to be_a String @@ -55,12 +56,15 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do expect(json_response['group_runner_token_expiration_interval']).to be_nil expect(json_response['project_runner_token_expiration_interval']).to be_nil expect(json_response['max_export_size']).to eq(0) + expect(json_response['max_terraform_state_size_bytes']).to eq(0) expect(json_response['pipeline_limit_per_project_user_sha']).to eq(0) expect(json_response['delete_inactive_projects']).to be(false) expect(json_response['inactive_projects_delete_after_months']).to eq(2) expect(json_response['inactive_projects_min_size_mb']).to eq(0) expect(json_response['inactive_projects_send_warning_email_after_months']).to eq(1) expect(json_response['can_create_group']).to eq(true) + expect(json_response['jira_connect_application_key']).to eq(nil) + expect(json_response['jira_connect_proxy_url']).to eq(nil) end end @@ -146,6 +150,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do mailgun_events_enabled: true, mailgun_signing_key: 'MAILGUN_SIGNING_KEY', max_export_size: 6, + max_terraform_state_size_bytes: 1_000, disabled_oauth_sign_in_sources: 'unknown', import_sources: 'github,bitbucket', wiki_page_max_content_bytes: 12345, @@ -158,7 +163,10 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do inactive_projects_delete_after_months: 24, inactive_projects_min_size_mb: 10, inactive_projects_send_warning_email_after_months: 12, - can_create_group: false + can_create_group: false, + jira_connect_application_key: '123', + jira_connect_proxy_url: 'http://example.com', + bulk_import_enabled: false } expect(response).to have_gitlab_http_status(:ok) @@ -207,6 +215,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do expect(json_response['mailgun_events_enabled']).to be(true) expect(json_response['mailgun_signing_key']).to eq('MAILGUN_SIGNING_KEY') expect(json_response['max_export_size']).to eq(6) + expect(json_response['max_terraform_state_size_bytes']).to eq(1_000) expect(json_response['disabled_oauth_sign_in_sources']).to eq([]) expect(json_response['import_sources']).to match_array(%w(github bitbucket)) expect(json_response['wiki_page_max_content_bytes']).to eq(12345) @@ -220,6 +229,9 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do expect(json_response['inactive_projects_min_size_mb']).to eq(10) expect(json_response['inactive_projects_send_warning_email_after_months']).to eq(12) expect(json_response['can_create_group']).to eq(false) + expect(json_response['jira_connect_application_key']).to eq('123') + expect(json_response['jira_connect_proxy_url']).to eq('http://example.com') + expect(json_response['bulk_import_enabled']).to be(false) end end diff --git a/spec/requests/api/sidekiq_metrics_spec.rb b/spec/requests/api/sidekiq_metrics_spec.rb index 302d824e650..1085df97cc7 100644 --- a/spec/requests/api/sidekiq_metrics_spec.rb +++ b/spec/requests/api/sidekiq_metrics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::SidekiqMetrics do +RSpec.describe API::SidekiqMetrics, feature_category: :not_owned do let(:admin) { create(:user, :admin) } describe 'GET sidekiq/*' do diff --git a/spec/requests/api/snippet_repository_storage_moves_spec.rb b/spec/requests/api/snippet_repository_storage_moves_spec.rb index 40d01500ac1..6081531aee9 100644 --- a/spec/requests/api/snippet_repository_storage_moves_spec.rb +++ b/spec/requests/api/snippet_repository_storage_moves_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::SnippetRepositoryStorageMoves do +RSpec.describe API::SnippetRepositoryStorageMoves, feature_category: :gitaly do it_behaves_like 'repository_storage_moves API', 'snippets' do let_it_be(:container) { create(:snippet, :repository).tap { |snippet| snippet.create_repository } } let_it_be(:storage_move) { create(:snippet_repository_storage_move, :scheduled, container: container) } diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index 9408d1cc248..dd0da0cb887 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Snippets, factory_default: :keep do +RSpec.describe API::Snippets, factory_default: :keep, feature_category: :source_code_management do include SnippetHelpers let_it_be(:admin) { create(:user, :admin) } diff --git a/spec/requests/api/statistics_spec.rb b/spec/requests/api/statistics_spec.rb index baffb2792e9..85fed48a077 100644 --- a/spec/requests/api/statistics_spec.rb +++ b/spec/requests/api/statistics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Statistics, 'Statistics' do +RSpec.describe API::Statistics, 'Statistics', feature_category: :devops_reports 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 9840476ca27..7b041ab7c4b 100644 --- a/spec/requests/api/submodules_spec.rb +++ b/spec/requests/api/submodules_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Submodules do +RSpec.describe API::Submodules, feature_category: :source_code_management 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 dfc5d169af6..93b2435c601 100644 --- a/spec/requests/api/suggestions_spec.rb +++ b/spec/requests/api/suggestions_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Suggestions do +RSpec.describe API::Suggestions, feature_category: :code_review 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 0f1dbea2e73..51edf4b3b3e 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::SystemHooks do +RSpec.describe API::SystemHooks, feature_category: :integrations do let_it_be(:non_admin) { create(:user) } let_it_be(:admin) { create(:admin) } let_it_be_with_refind(:hook) { create(:system_hook, url: "http://example.com") } diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 3f2ca2a0938..b02c7135b7b 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Tags do +RSpec.describe API::Tags, feature_category: :source_code_management 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') } @@ -479,4 +479,60 @@ RSpec.describe API::Tags do end end end + + describe 'GET /projects/:id/repository/tags/:tag_name/signature' do + let_it_be(:project) { create(:project, :repository, :public) } + let(:project_id) { project.id } + let(:route) { "/projects/#{project_id}/repository/tags/#{tag_name}/signature" } + + context 'when tag does not exist' do + let(:tag_name) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 Tag Not Found' } + end + end + + context 'unsigned tag' do + let(:tag_name) { 'v1.1.0' } + + it_behaves_like '404 response' do + let(:request) { get api(route, current_user) } + let(:message) { '404 Signature Not Found' } + end + end + + context 'x509 signed tag' do + let(:tag_name) { 'v1.1.1' } + let(:tag) { project.repository.find_tag(tag_name) } + let(:signature) { tag.signature } + let(:x509_certificate) { signature.x509_certificate } + let(:x509_issuer) { x509_certificate.x509_issuer } + + it 'returns correct JSON' do + get api(route, current_user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq( + 'signature_type' => 'X509', + 'verification_status' => signature.verification_status.to_s, + 'x509_certificate' => { + 'id' => x509_certificate.id, + 'subject' => x509_certificate.subject, + 'subject_key_identifier' => x509_certificate.subject_key_identifier, + 'email' => x509_certificate.email, + 'serial_number' => x509_certificate.serial_number, + 'certificate_status' => x509_certificate.certificate_status, + 'x509_issuer' => { + 'id' => x509_issuer.id, + 'subject' => x509_issuer.subject, + 'subject_key_identifier' => x509_issuer.subject_key_identifier, + 'crl_url' => x509_issuer.crl_url + } + } + ) + end + end + end end diff --git a/spec/requests/api/task_completion_status_spec.rb b/spec/requests/api/task_completion_status_spec.rb index 97ce858ba12..c46d6954da3 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' -RSpec.describe 'task completion status response' do +RSpec.describe 'task completion status response', features: :team_planning do let_it_be(:user) { create(:user) } let_it_be(:project) do create(:project, :public, creator_id: user.id, namespace: user.namespace) @@ -10,44 +10,44 @@ RSpec.describe 'task completion status response' do shared_examples 'taskable completion status provider' do |path| samples = [ - { - description: '', - expected_count: 0, - expected_completed_count: 0 - }, - { - description: 'Lorem ipsum', - expected_count: 0, - expected_completed_count: 0 - }, - { - description: %{- [ ] task 1 + { + description: '', + expected_count: 0, + expected_completed_count: 0 + }, + { + description: 'Lorem ipsum', + expected_count: 0, + expected_completed_count: 0 + }, + { + description: %{- [ ] task 1 - [x] task 2 }, - expected_count: 2, - expected_completed_count: 1 - }, - { - description: %{- [ ] task 1 + expected_count: 2, + expected_completed_count: 1 + }, + { + description: %{- [ ] task 1 - [ ] task 2 }, - expected_count: 2, - expected_completed_count: 0 - }, - { - description: %{- [x] task 1 + expected_count: 2, + expected_completed_count: 0 + }, + { + description: %{- [x] task 1 - [x] task 2 }, - expected_count: 2, - expected_completed_count: 2 - }, - { - description: %{- [ ] task 1}, - expected_count: 1, - expected_completed_count: 0 - }, - { - description: %{- [x] task 1}, - expected_count: 1, - expected_completed_count: 1 - } + expected_count: 2, + expected_completed_count: 2 + }, + { + description: %{- [ ] task 1}, + expected_count: 1, + expected_completed_count: 0 + }, + { + description: %{- [x] task 1}, + expected_count: 1, + expected_completed_count: 1 + } ] samples.each do |sample_data| context "with a description of #{sample_data[:description].inspect}" do diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb index adb37c62dc3..8782c3cba4b 100644 --- a/spec/requests/api/templates_spec.rb +++ b/spec/requests/api/templates_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Templates do +RSpec.describe API::Templates, feature_category: :source_code_management do context 'the Template Entity' do before do get api('/templates/gitignores/Ruby') diff --git a/spec/requests/api/terraform/modules/v1/packages_spec.rb b/spec/requests/api/terraform/modules/v1/packages_spec.rb index ae61017f5bb..2bd7cb027aa 100644 --- a/spec/requests/api/terraform/modules/v1/packages_spec.rb +++ b/spec/requests/api/terraform/modules/v1/packages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Terraform::Modules::V1::Packages do +RSpec.describe API::Terraform::Modules::V1::Packages, feature_category: :package_registry do include PackagesManagerApiSpecHelpers include WorkhorseHelpers using RSpec::Parameterized::TableSyntax @@ -418,7 +418,8 @@ RSpec.describe API::Terraform::Modules::V1::Packages do { project: project, user: user_role == :anonymous ? nil : user, - namespace: project.namespace + namespace: project.namespace, + property: 'i_package_terraform_module_user' } end @@ -583,7 +584,10 @@ RSpec.describe API::Terraform::Modules::V1::Packages do with_them do let(:user_headers) { user_role == :anonymous ? {} : { token_header => token } } let(:headers) { user_headers.merge(workhorse_headers) } - let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: snowplow_user } } + let(:snowplow_gitlab_standard_context) do + { project: project, namespace: project.namespace, user: snowplow_user, property: 'i_package_terraform_module_user' } + end + let(:snowplow_user) do case token_type when :deploy_token diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb index 38b08b4e214..fd34345d814 100644 --- a/spec/requests/api/terraform/state_spec.rb +++ b/spec/requests/api/terraform/state_spec.rb @@ -2,20 +2,22 @@ require 'spec_helper' -RSpec.describe API::Terraform::State, :snowplow do +RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructure_as_code do include HttpBasicAuthHelpers let_it_be(:project) { create(:project) } let_it_be(:developer) { create(:user, developer_projects: [project]) } let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) } - let!(:state) { create(:terraform_state, :with_version, project: project) } - let(:current_user) { maintainer } let(:auth_header) { user_basic_auth_header(current_user) } let(:project_id) { project.id } - let(:state_name) { state.name } + + let(:state_name) { "some-state" } let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name}" } + let!(:state) do + create(:terraform_state, :with_version, project: project, name: URI.decode_www_form_component(state_name)) + end before do stub_terraform_state_object_storage @@ -91,15 +93,35 @@ RSpec.describe API::Terraform::State, :snowplow do end end - context 'personal acceess token authentication' do + shared_examples 'can access terraform state' do + it 'returns terraform state of a project of given state name' do + request + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to eq(state.reload.latest_file.read) + end + end + + context 'personal access token authentication' do context 'with maintainer permissions' do let(:current_user) { maintainer } - it 'returns terraform state belonging to a project of given state name' do - request + where(given_state_name: %w[test-state test.state test%2Ffoo]) + with_them do + it_behaves_like 'can access terraform state' do + let(:state_name) { given_state_name } + end + end - expect(response).to have_gitlab_http_status(:ok) - expect(response.body).to eq(state.reload.latest_file.read) + context 'allow_dots_on_tf_state_names is disabled, and the state name contains a dot' do + let(:state_name) { 'state-name-with-dot' } + let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name}.tfstate" } + + before do + stub_feature_flags(allow_dots_on_tf_state_names: false) + end + + it_behaves_like 'can access terraform state' end context 'for a project that does not exist' do @@ -112,18 +134,23 @@ RSpec.describe API::Terraform::State, :snowplow do end end + context 'with invalid state name' do + let(:state_name) { 'foo/bar' } + + it 'returns a 404 error' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + it_behaves_like 'cannot access a state that is scheduled for deletion' end context 'with developer permissions' do let(:current_user) { developer } - it 'returns terraform state belonging to a project of given state name' do - request - - expect(response).to have_gitlab_http_status(:ok) - expect(response.body).to eq(state.reload.latest_file.read) - end + it_behaves_like 'can access terraform state' end end @@ -133,12 +160,7 @@ RSpec.describe API::Terraform::State, :snowplow do context 'with maintainer permissions' do let(:job) { create(:ci_build, status: :running, project: project, user: maintainer) } - it 'returns terraform state belonging to a project of given state name' do - request - - expect(response).to have_gitlab_http_status(:ok) - expect(response.body).to eq(state.reload.latest_file.read) - end + it_behaves_like 'can access terraform state' it 'returns unauthorized if the the job is not running' do job.update!(status: :failed) @@ -161,12 +183,7 @@ RSpec.describe API::Terraform::State, :snowplow do context 'with developer permissions' do let(:job) { create(:ci_build, status: :running, project: project, user: developer) } - it 'returns terraform state belonging to a project of given state name' do - request - - expect(response).to have_gitlab_http_status(:ok) - expect(response.body).to eq(state.reload.latest_file.read) - end + it_behaves_like 'can access terraform state' end end end @@ -182,11 +199,26 @@ RSpec.describe API::Terraform::State, :snowplow do context 'with maintainer permissions' do let(:current_user) { maintainer } - it 'updates the state' do - expect { request }.to change { Terraform::State.count }.by(0) + where(given_state_name: %w[test-state test.state test%2Ffoo]) + with_them do + let(:state_name) { given_state_name } - expect(response).to have_gitlab_http_status(:ok) - expect(Gitlab::Json.parse(response.body)).to be_empty + it 'updates the state' do + expect { request }.to change { Terraform::State.count }.by(0) + + expect(response).to have_gitlab_http_status(:ok) + expect(Gitlab::Json.parse(response.body)).to be_empty + end + end + + context 'with invalid state name' do + let(:state_name) { 'foo/bar' } + + it 'returns a 404 error' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end end context 'when serial already exists' do @@ -224,16 +256,39 @@ RSpec.describe API::Terraform::State, :snowplow do end context 'when there is no terraform state of a given name' do - let(:state_name) { 'example2' } + let(:non_existing_state_name) { 'non-existing-state' } + let(:non_existing_state_path) { "/projects/#{project_id}/terraform/state/#{non_existing_state_name}" } + + subject(:request) { post api(non_existing_state_path), headers: auth_header, as: :json, params: params } context 'with maintainer permissions' do let(:current_user) { maintainer } - it 'creates a new state' do - expect { request }.to change { Terraform::State.count }.by(1) + where(given_state_name: %w[test-state test.state test%2Ffoo]) + with_them do + let(:state_name) { given_state_name } - expect(response).to have_gitlab_http_status(:ok) - expect(Gitlab::Json.parse(response.body)).to be_empty + it 'creates a new state' do + expect { request }.to change { Terraform::State.count }.by(1) + + expect(response).to have_gitlab_http_status(:ok) + expect(Gitlab::Json.parse(response.body)).to be_empty + end + end + + context 'allow_dots_on_tf_state_names is disabled, and the state name contains a dot' do + let(:non_existing_state_name) { 'state-name-with-dot.tfstate' } + + before do + stub_feature_flags(allow_dots_on_tf_state_names: false) + end + + it 'strips characters after the dot' do + expect { request }.to change { Terraform::State.count }.by(1) + + expect(response).to have_gitlab_http_status(:ok) + expect(Terraform::State.last.name).to eq('state-name-with-dot') + end end end @@ -269,6 +324,48 @@ RSpec.describe API::Terraform::State, :snowplow do expect(state.reload_latest_version.build).to eq(job) end end + + describe 'response depending on the max allowed state size' do + let(:current_user) { maintainer } + + before do + stub_application_setting(max_terraform_state_size_bytes: max_allowed_state_size) + + request + end + + context 'when the max allowed state size is unlimited (set as 0)' do + let(:max_allowed_state_size) { 0 } + + it 'returns a success response' do + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when the max allowed state size is greater than the request state size' do + let(:max_allowed_state_size) { params.to_json.size + 1 } + + it 'returns a success response' do + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when the max allowed state size is equal to the request state size' do + let(:max_allowed_state_size) { params.to_json.size } + + it 'returns a success response' do + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when the max allowed state size is less than the request state size' do + let(:max_allowed_state_size) { params.to_json.size - 1 } + + it "returns a 'payload too large' response" do + expect(response).to have_gitlab_http_status(:payload_too_large) + end + end + end end describe 'DELETE /projects/:id/terraform/state/:name' do @@ -276,11 +373,8 @@ RSpec.describe API::Terraform::State, :snowplow do it_behaves_like 'endpoint with unique user tracking' - context 'with maintainer permissions' do - let(:current_user) { maintainer } - let(:deletion_service) { instance_double(Terraform::States::TriggerDestroyService) } - - it 'schedules the state for deletion and returns empty body' do + shared_examples 'schedules the state for deletion' do + it 'returns empty body' do expect(Terraform::States::TriggerDestroyService).to receive(:new).and_return(deletion_service) expect(deletion_service).to receive(:execute).once @@ -289,6 +383,40 @@ RSpec.describe API::Terraform::State, :snowplow do expect(response).to have_gitlab_http_status(:ok) expect(Gitlab::Json.parse(response.body)).to be_empty end + end + + context 'with maintainer permissions' do + let(:current_user) { maintainer } + let(:deletion_service) { instance_double(Terraform::States::TriggerDestroyService) } + + where(given_state_name: %w[test-state test.state test%2Ffoo]) + with_them do + let(:state_name) { given_state_name } + + it_behaves_like 'schedules the state for deletion' + end + + context 'allow_dots_on_tf_state_names is disabled, and the state name contains a dot' do + let(:state_name) { 'state-name-with-dot' } + let(:state_name_with_dot) { "#{state_name}.tfstate" } + let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name_with_dot}" } + + before do + stub_feature_flags(allow_dots_on_tf_state_names: false) + end + + it_behaves_like 'schedules the state for deletion' + end + + context 'with invalid state name' do + let(:state_name) { 'foo/bar' } + + it 'returns a 404 error' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end it_behaves_like 'cannot access a state that is scheduled for deletion' end @@ -304,7 +432,7 @@ RSpec.describe API::Terraform::State, :snowplow do end end - describe 'PUT /projects/:id/terraform/state/:name/lock' do + describe 'POST /projects/:id/terraform/state/:name/lock' do let(:params) do { ID: '123-456', @@ -322,10 +450,14 @@ RSpec.describe API::Terraform::State, :snowplow do it_behaves_like 'endpoint with unique user tracking' it_behaves_like 'cannot access a state that is scheduled for deletion' - it 'locks the terraform state' do - request + context 'with invalid state name' do + let(:state_name) { 'foo/bar' } - expect(response).to have_gitlab_http_status(:ok) + it 'returns a 404 error' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end end context 'state is already locked' do @@ -349,6 +481,47 @@ RSpec.describe API::Terraform::State, :snowplow do expect(response).to have_gitlab_http_status(:forbidden) end end + + where(given_state_name: %w[test-state test%2Ffoo]) + with_them do + let(:state_name) { given_state_name } + + it 'locks the terraform state' do + request + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'with a dot in the state name' do + let(:state_name) { 'test.state' } + + context 'with allow_dots_on_tf_state_names ff enabled' do + before do + stub_feature_flags(allow_dots_on_tf_state_names: true) + end + + let(:state_name) { 'test.state' } + + it 'locks the terraform state' do + request + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'with allow_dots_on_tf_state_names ff disabled' do + before do + stub_feature_flags(allow_dots_on_tf_state_names: false) + end + + it 'returns 404' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end end describe 'DELETE /projects/:id/terraform/state/:name/lock' do @@ -365,8 +538,9 @@ RSpec.describe API::Terraform::State, :snowplow do end before do - state.lock_xid = '123-456' + state.lock_xid = '123.456' state.save! + stub_feature_flags(allow_dots_on_tf_state_names: true) end subject(:request) { delete api("#{state_path}/lock"), headers: auth_header, params: params } @@ -379,28 +553,61 @@ RSpec.describe API::Terraform::State, :snowplow do let(:lock_id) { 'irrelevant to this test, just needs to be present' } end - context 'with the correct lock id' do - let(:lock_id) { '123-456' } + where(given_state_name: %w[test-state test.state test%2Ffoo]) + with_them do + let(:state_name) { given_state_name } - it 'removes the terraform state lock' do - request + context 'with the correct lock id' do + let(:lock_id) { '123.456' } - expect(response).to have_gitlab_http_status(:ok) + it 'removes the terraform state lock' do + request + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'with allow_dots_on_tf_state_names ff disabled' do + before do + stub_feature_flags(allow_dots_on_tf_state_names: false) + end + + context 'with dots in the state name' do + let(:lock_id) { '123.456' } + let(:state_name) { 'test.state' } + + it 'returns 404' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + context 'with no lock id (force-unlock)' do + let(:params) { {} } + + it 'removes the terraform state lock' do + request + + expect(response).to have_gitlab_http_status(:ok) + end end end - context 'with no lock id (force-unlock)' do - let(:params) { {} } + context 'with invalid state name' do + let(:lock_id) { '123.456' } + let(:state_name) { 'foo/bar' } - it 'removes the terraform state lock' do + it 'returns a 404 error' do request - expect(response).to have_gitlab_http_status(:ok) + expect(response).to have_gitlab_http_status(:not_found) end end context 'with an incorrect lock id' do - let(:lock_id) { '456-789' } + let(:lock_id) { '456.789' } it 'returns an error' do request @@ -420,7 +627,7 @@ RSpec.describe API::Terraform::State, :snowplow do end context 'user does not have permission to unlock the state' do - let(:lock_id) { '123-456' } + let(:lock_id) { '123.456' } let(:current_user) { developer } it 'returns an error' do diff --git a/spec/requests/api/terraform/state_version_spec.rb b/spec/requests/api/terraform/state_version_spec.rb index ade0aacf805..28abbb5749d 100644 --- a/spec/requests/api/terraform/state_version_spec.rb +++ b/spec/requests/api/terraform/state_version_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Terraform::StateVersion do +RSpec.describe API::Terraform::StateVersion, feature_category: :infrastructure_as_code do include HttpBasicAuthHelpers let_it_be(:project) { create(:project) } diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 7a626ee4d29..5a342f79926 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Todos do +RSpec.describe API::Todos, feature_category: :source_code_management do include DesignManagementTestHelpers let_it_be(:group) { create(:group) } @@ -15,6 +15,7 @@ RSpec.describe API::Todos do let_it_be(:work_item) { create(:work_item, :task, project: project_1) } let_it_be(:merge_request) { create(:merge_request, source_project: project_1) } let_it_be(:alert) { create(:alert_management_alert, project: project_1) } + let_it_be(:group_request_todo) { create(:todo, author: author_1, user: john_doe, target: group, action: Todo::MEMBER_ACCESS_REQUESTED) } let_it_be(:alert_todo) { create(:todo, project: project_1, author: john_doe, user: john_doe, target: alert) } let_it_be(:merge_request_todo) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } let_it_be(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe, target: issue) } @@ -71,7 +72,7 @@ RSpec.describe API::Todos 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(6) + expect(json_response.length).to eq(7) expect(json_response[0]).to include( 'id' => pending_5.id, @@ -127,6 +128,17 @@ RSpec.describe API::Todos do 'title' => alert.title ) ) + + expect(json_response[6]).to include( + 'target_type' => 'Namespace', + 'action_name' => 'member_access_requested', + 'target' => hash_including( + 'id' => group.id, + 'name' => group.name, + 'full_path' => group.full_path + ), + 'target_url' => Gitlab::Routing.url_helpers.group_group_members_url(group, tab: 'access_requests') + ) end context "when current user does not have access to one of the TODO's target" do @@ -137,7 +149,7 @@ RSpec.describe API::Todos do get api('/todos', john_doe) - expect(json_response.count).to eq(6) + expect(json_response.count).to eq(7) expect(json_response.map { |t| t['id'] }).not_to include(no_access_todo.id, pending_4.id) end end @@ -231,7 +243,7 @@ RSpec.describe API::Todos do create(:on_commit_todo, project: new_todo.project, author: author_1, user: john_doe, target: merge_request_3) create(:todo, project: new_todo.project, author: author_2, user: john_doe, target: merge_request_3) - expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control1).with_threshold(5) + expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control1).with_threshold(6) control2 = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) } create_issue_todo_for(john_doe) diff --git a/spec/requests/api/topics_spec.rb b/spec/requests/api/topics_spec.rb index 1ad6f876fab..14719292557 100644 --- a/spec/requests/api/topics_spec.rb +++ b/spec/requests/api/topics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Topics do +RSpec.describe API::Topics, feature_category: :projects do include WorkhorseHelpers let_it_be(:file) { fixture_file_upload('spec/fixtures/dk.png') } diff --git a/spec/requests/api/unleash_spec.rb b/spec/requests/api/unleash_spec.rb index 4d382f91023..5daf7cd7b75 100644 --- a/spec/requests/api/unleash_spec.rb +++ b/spec/requests/api/unleash_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Unleash do +RSpec.describe API::Unleash, feature_category: :feature_flags do include FeatureFlagHelpers let_it_be(:project, refind: true) { create(:project) } @@ -88,85 +88,6 @@ RSpec.describe API::Unleash do end end - shared_examples_for 'support multiple environments' do - let!(:client) { create(:operations_feature_flags_client, project: project) } - let!(:base_headers) { { "UNLEASH-INSTANCEID" => client.token } } - let!(:headers) { base_headers.merge({ "UNLEASH-APPNAME" => "test" }) } - - let!(:feature_flag_1) do - create(:operations_feature_flag, name: "feature_flag_1", project: project, active: true) - end - - let!(:feature_flag_2) do - create(:operations_feature_flag, name: "feature_flag_2", project: project, active: false) - end - - before do - create_scope(feature_flag_1, 'production', false) - create_scope(feature_flag_2, 'review/*', true) - end - - it 'does not have N+1 problem' do - control_count = ActiveRecord::QueryRecorder.new { get api(features_url), headers: headers }.count - - create(:operations_feature_flag, name: "feature_flag_3", project: project, active: true) - - expect { get api(features_url), headers: headers }.not_to exceed_query_limit(control_count) - end - - context 'when app name is staging' do - let(:headers) { base_headers.merge({ "UNLEASH-APPNAME" => "staging" }) } - - it 'returns correct active values' do - subject - - feature_flag_1 = json_response['features'].find { |f| f['name'] == 'feature_flag_1' } - feature_flag_2 = json_response['features'].find { |f| f['name'] == 'feature_flag_2' } - - expect(feature_flag_1['enabled']).to eq(true) - expect(feature_flag_2['enabled']).to eq(false) - end - end - - context 'when app name is production' do - let(:headers) { base_headers.merge({ "UNLEASH-APPNAME" => "production" }) } - - it 'returns correct active values' do - subject - - feature_flag_1 = json_response['features'].find { |f| f['name'] == 'feature_flag_1' } - feature_flag_2 = json_response['features'].find { |f| f['name'] == 'feature_flag_2' } - - expect(feature_flag_1['enabled']).to eq(false) - expect(feature_flag_2['enabled']).to eq(false) - end - end - - context 'when app name is review/patch-1' do - let(:headers) { base_headers.merge({ "UNLEASH-APPNAME" => "review/patch-1" }) } - - it 'returns correct active values' do - subject - - feature_flag_1 = json_response['features'].find { |f| f['name'] == 'feature_flag_1' } - feature_flag_2 = json_response['features'].find { |f| f['name'] == 'feature_flag_2' } - - expect(feature_flag_1['enabled']).to eq(true) - expect(feature_flag_2['enabled']).to eq(false) - end - end - - context 'when app name is empty' do - let(:headers) { base_headers } - - it 'returns empty list' do - subject - - expect(json_response['features'].count).to eq(0) - end - end - end - %w(/feature_flags/unleash/:project_id/features /feature_flags/unleash/:project_id/client/features).each do |features_endpoint| describe "GET #{features_endpoint}", :use_clean_rails_redis_caching do let(:features_url) { features_endpoint.sub(':project_id', project_id.to_s) } diff --git a/spec/requests/api/usage_data_non_sql_metrics_spec.rb b/spec/requests/api/usage_data_non_sql_metrics_spec.rb index 0b73d0f96a4..0a6f248af2c 100644 --- a/spec/requests/api/usage_data_non_sql_metrics_spec.rb +++ b/spec/requests/api/usage_data_non_sql_metrics_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::UsageDataNonSqlMetrics do +RSpec.describe API::UsageDataNonSqlMetrics, feature_category: :service_ping do include UsageDataHelpers let_it_be(:admin) { create(:user, admin: true) } diff --git a/spec/requests/api/usage_data_queries_spec.rb b/spec/requests/api/usage_data_queries_spec.rb index 6ce03954246..e556064025c 100644 --- a/spec/requests/api/usage_data_queries_spec.rb +++ b/spec/requests/api/usage_data_queries_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require 'rake_helper' -RSpec.describe API::UsageDataQueries do +RSpec.describe API::UsageDataQueries, feature_category: :service_ping do include UsageDataHelpers let_it_be(:admin) { create(:user, admin: true) } @@ -80,7 +80,7 @@ RSpec.describe API::UsageDataQueries do end it 'matches the generated query' do - Timecop.freeze(2021, 1, 1) do + travel_to(Time.utc(2021, 1, 1)) do get api(endpoint, admin) end diff --git a/spec/requests/api/usage_data_spec.rb b/spec/requests/api/usage_data_spec.rb index d532fb6c168..935ddbf4764 100644 --- a/spec/requests/api/usage_data_spec.rb +++ b/spec/requests/api/usage_data_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::UsageData do +RSpec.describe API::UsageData, feature_category: :service_ping do let_it_be(:user) { create(:user) } describe 'POST /usage_data/increment_counter' do diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb index 369ae49de08..27e5311e2eb 100644 --- a/spec/requests/api/user_counts_spec.rb +++ b/spec/requests/api/user_counts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::UserCounts do +RSpec.describe API::UserCounts, feature_category: :service_ping do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public) } let_it_be(:issue) { create(:issue, project: project, author: user, assignees: [user]) } diff --git a/spec/requests/api/users_preferences_spec.rb b/spec/requests/api/users_preferences_spec.rb index 97e37263ee6..53f366371e5 100644 --- a/spec/requests/api/users_preferences_spec.rb +++ b/spec/requests/api/users_preferences_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Users do +RSpec.describe API::Users, feature_category: :users do let_it_be(:user) { create(:user) } describe 'PUT /user/preferences/' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 6688a998a1a..bfb71d95f5e 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Users do +RSpec.describe API::Users, feature_category: :users do include WorkhorseHelpers let_it_be(:admin) { create(:admin) } @@ -1988,11 +1988,19 @@ RSpec.describe API::Users do expect(json_response['error']).to eq('title is missing') end - it "creates ssh key" do - key_attrs = attributes_for :key + it "creates ssh key", :aggregate_failures do + key_attrs = attributes_for(:key, usage_type: :signing) + expect do post api("/users/#{user.id}/keys", admin), params: key_attrs end.to change { user.keys.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + + key = user.keys.last + expect(key.title).to eq(key_attrs[:title]) + expect(key.key).to eq(key_attrs[:key]) + expect(key.usage_type).to eq(key_attrs[:usage_type].to_s) end it 'creates SSH key with `expires_at` attribute' do @@ -2848,12 +2856,19 @@ RSpec.describe API::Users do end describe "POST /user/keys" do - it "creates ssh key" do - key_attrs = attributes_for :key + it "creates ssh key", :aggregate_failures do + key_attrs = attributes_for(:key, usage_type: :signing) + expect do post api("/user/keys", user), params: key_attrs end.to change { user.keys.count }.by(1) + expect(response).to have_gitlab_http_status(:created) + + key = user.keys.last + expect(key.title).to eq(key_attrs[:title]) + expect(key.key).to eq(key_attrs[:key]) + expect(key.usage_type).to eq(key_attrs[:usage_type].to_s) end it 'creates SSH key with `expires_at` attribute' do diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb index 5bfea15f0ca..0b8fac5c55c 100644 --- a/spec/requests/api/v3/github_spec.rb +++ b/spec/requests/api/v3/github_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::V3::Github do +RSpec.describe API::V3::Github, feature_category: :integrations do let_it_be(:user) { create(:user) } let_it_be(:unauthorized_user) { create(:user) } let_it_be(:admin) { create(:user, :admin) } diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb index f4096eef8d0..00e38a5bb7e 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. -RSpec.describe API::Wikis do +RSpec.describe API::Wikis, feature_category: :wiki do include WorkhorseHelpers include AfterNextHelpers diff --git a/spec/requests/concerns/planning_hierarchy_spec.rb b/spec/requests/concerns/planning_hierarchy_spec.rb index ece9270b3a1..89232916936 100644 --- a/spec/requests/concerns/planning_hierarchy_spec.rb +++ b/spec/requests/concerns/planning_hierarchy_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PlanningHierarchy, type: :request do +RSpec.describe PlanningHierarchy, type: :request, feature_category: :projects do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } diff --git a/spec/requests/content_security_policy_spec.rb b/spec/requests/content_security_policy_spec.rb index 06fc5b0e190..3f0665f1ce5 100644 --- a/spec/requests/content_security_policy_spec.rb +++ b/spec/requests/content_security_policy_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' # The AnonymousController doesn't support setting the CSP # This is why an arbitrary test request was chosen instead # of testing in application_controller_spec. -RSpec.describe 'Content Security Policy' do +RSpec.describe 'Content Security Policy', feature_category: :application_instrumentation do let(:snowplow_host) { 'snowplow.example.com' } shared_examples 'snowplow is not in the CSP' do diff --git a/spec/requests/dashboard/projects_controller_spec.rb b/spec/requests/dashboard/projects_controller_spec.rb index 4cd3b6c4f9e..752799196c9 100644 --- a/spec/requests/dashboard/projects_controller_spec.rb +++ b/spec/requests/dashboard/projects_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Dashboard::ProjectsController do +RSpec.describe Dashboard::ProjectsController, feature_category: :projects do context 'token authentication' do it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false do let(:url) { dashboard_projects_url(:atom) } diff --git a/spec/requests/dashboard_controller_spec.rb b/spec/requests/dashboard_controller_spec.rb index 62655d720c5..9edacb27c93 100644 --- a/spec/requests/dashboard_controller_spec.rb +++ b/spec/requests/dashboard_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe DashboardController do +RSpec.describe DashboardController, feature_category: :authentication_and_authorization do context 'token authentication' do it_behaves_like 'authenticates sessionless user for the request spec', 'issues atom', public_resource: false do let(:url) { issues_dashboard_url(:atom, assignee_username: user.username) } diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 20d298edfe5..66337b94c75 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Git HTTP requests' do +RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do include ProjectForksHelper include TermsHelper include GitHttpHelpers diff --git a/spec/requests/groups/autocomplete_sources_spec.rb b/spec/requests/groups/autocomplete_sources_spec.rb index d053e0fe773..e44fb9f6c37 100644 --- a/spec/requests/groups/autocomplete_sources_spec.rb +++ b/spec/requests/groups/autocomplete_sources_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'groups autocomplete' do +RSpec.describe 'groups autocomplete', feature_category: :subgroups do let_it_be(:user) { create(:user) } let_it_be_with_reload(:group) { create(:group, :private) } diff --git a/spec/requests/groups/clusters/integrations_controller_spec.rb b/spec/requests/groups/clusters/integrations_controller_spec.rb index 29e37e2e48c..0b9148e917b 100644 --- a/spec/requests/groups/clusters/integrations_controller_spec.rb +++ b/spec/requests/groups/clusters/integrations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::Clusters::IntegrationsController do +RSpec.describe Groups::Clusters::IntegrationsController, features: :integrations do include AccessMatchersForController shared_examples 'a secure endpoint' do diff --git a/spec/requests/groups/crm/contacts_controller_spec.rb b/spec/requests/groups/crm/contacts_controller_spec.rb index 70086ddbbba..4916ce60108 100644 --- a/spec/requests/groups/crm/contacts_controller_spec.rb +++ b/spec/requests/groups/crm/contacts_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::Crm::ContactsController do +RSpec.describe Groups::Crm::ContactsController, feature_category: :team_planning do let_it_be(:user) { create(:user) } shared_examples 'response with 404 status' do diff --git a/spec/requests/groups/crm/organizations_controller_spec.rb b/spec/requests/groups/crm/organizations_controller_spec.rb index e841dd80b67..3e7e9a8e878 100644 --- a/spec/requests/groups/crm/organizations_controller_spec.rb +++ b/spec/requests/groups/crm/organizations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::Crm::OrganizationsController do +RSpec.describe Groups::Crm::OrganizationsController, feature_category: :team_planning do let_it_be(:user) { create(:user) } shared_examples 'response with 404 status' do diff --git a/spec/requests/groups/deploy_tokens_controller_spec.rb b/spec/requests/groups/deploy_tokens_controller_spec.rb index b3dce9b9cf1..05fd8d9691c 100644 --- a/spec/requests/groups/deploy_tokens_controller_spec.rb +++ b/spec/requests/groups/deploy_tokens_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::DeployTokensController do +RSpec.describe Groups::DeployTokensController, feature_category: :continuous_delivery do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } let_it_be(:deploy_token) { create(:deploy_token, :group, groups: [group]) } diff --git a/spec/requests/groups/email_campaigns_controller_spec.rb b/spec/requests/groups/email_campaigns_controller_spec.rb index 4d630ef6710..7db5c084793 100644 --- a/spec/requests/groups/email_campaigns_controller_spec.rb +++ b/spec/requests/groups/email_campaigns_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::EmailCampaignsController do +RSpec.describe Groups::EmailCampaignsController, feature_category: :navigation do using RSpec::Parameterized::TableSyntax describe 'GET #index', :snowplow do diff --git a/spec/requests/groups/harbor/artifacts_controller_spec.rb b/spec/requests/groups/harbor/artifacts_controller_spec.rb index ea9529119a6..7ec792d081d 100644 --- a/spec/requests/groups/harbor/artifacts_controller_spec.rb +++ b/spec/requests/groups/harbor/artifacts_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::Harbor::ArtifactsController do +RSpec.describe Groups::Harbor::ArtifactsController, feature_category: :build_artifacts do it_behaves_like 'a harbor artifacts controller', anonymous_status_code: '404' do let_it_be(:container) { create(:group) } let_it_be(:harbor_integration) { create(:harbor_integration, group: container, project: nil) } diff --git a/spec/requests/groups/harbor/repositories_controller_spec.rb b/spec/requests/groups/harbor/repositories_controller_spec.rb index b4022561f54..f397e9192ea 100644 --- a/spec/requests/groups/harbor/repositories_controller_spec.rb +++ b/spec/requests/groups/harbor/repositories_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::Harbor::RepositoriesController do +RSpec.describe Groups::Harbor::RepositoriesController, feature_category: :source_code_management do it_behaves_like 'a harbor repositories controller', anonymous_status_code: '404' do let_it_be(:container, reload: true) { create(:group) } let_it_be(:harbor_integration) { create(:harbor_integration, group: container, project: nil) } diff --git a/spec/requests/groups/harbor/tags_controller_spec.rb b/spec/requests/groups/harbor/tags_controller_spec.rb index 257d4366837..15da1cb1c4b 100644 --- a/spec/requests/groups/harbor/tags_controller_spec.rb +++ b/spec/requests/groups/harbor/tags_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::Harbor::TagsController do +RSpec.describe Groups::Harbor::TagsController, feature_category: :source_code_management do it_behaves_like 'a harbor tags controller', anonymous_status_code: '404' do let_it_be(:container) { create(:group) } let_it_be(:harbor_integration) { create(:harbor_integration, group: container, project: nil) } diff --git a/spec/requests/groups/milestones_controller_spec.rb b/spec/requests/groups/milestones_controller_spec.rb index e6418c7694d..54a25333c02 100644 --- a/spec/requests/groups/milestones_controller_spec.rb +++ b/spec/requests/groups/milestones_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::MilestonesController do +RSpec.describe Groups::MilestonesController, feature_category: :team_planning do context 'N+1 DB queries' do let_it_be(:user) { create(:user) } let_it_be(:public_group) { create(:group, :public) } diff --git a/spec/requests/groups/observability_controller_spec.rb b/spec/requests/groups/observability_controller_spec.rb index a08231fe939..46690d60539 100644 --- a/spec/requests/groups/observability_controller_spec.rb +++ b/spec/requests/groups/observability_controller_spec.rb @@ -2,14 +2,13 @@ require 'spec_helper' -RSpec.describe Groups::ObservabilityController do - include ContentSecurityPolicyHelpers - +RSpec.describe Groups::ObservabilityController, feature_category: :tracing do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } let(:observability_url) { Gitlab::Observability.observability_url } - let(:expected_observability_path) { "/" } + let(:path) { nil } + let(:expected_observability_path) { nil } shared_examples 'observability route request' do subject do @@ -17,6 +16,10 @@ RSpec.describe Groups::ObservabilityController do response end + it_behaves_like 'observability csp policy' do + let(:tested_path) { path } + end + context 'when user is not authenticated' do it 'returns 404' do expect(subject).to have_gitlab_http_status(:not_found) @@ -70,101 +73,22 @@ RSpec.describe Groups::ObservabilityController do describe 'GET #dashboards' do let(:path) { group_observability_dashboards_path(group) } - let(:expected_observability_path) { "#{observability_url}/#{group.id}/" } + let(:expected_observability_path) { "#{observability_url}/-/#{group.id}/" } it_behaves_like 'observability route request' end describe 'GET #manage' do let(:path) { group_observability_manage_path(group) } - let(:expected_observability_path) { "#{observability_url}/#{group.id}/dashboards" } + let(:expected_observability_path) { "#{observability_url}/-/#{group.id}/dashboards" } it_behaves_like 'observability route request' end describe 'GET #explore' do let(:path) { group_observability_explore_path(group) } - let(:expected_observability_path) { "#{observability_url}/#{group.id}/explore" } + let(:expected_observability_path) { "#{observability_url}/-/#{group.id}/explore" } it_behaves_like 'observability route request' end - - describe 'CSP' do - before do - setup_csp_for_controller(described_class, csp) - end - - subject do - get group_observability_dashboards_path(group) - response.headers['Content-Security-Policy'] - end - - context 'when there is no CSP config' do - let(:csp) { ActionDispatch::ContentSecurityPolicy.new } - - it 'does not add any csp header' do - expect(subject).to be_blank - end - end - - context 'when frame-src exists in the CSP config' do - let(:csp) do - ActionDispatch::ContentSecurityPolicy.new do |p| - p.frame_src 'https://something.test' - end - end - - it 'appends the proper url to frame-src CSP directives' do - expect(subject).to include( - "frame-src https://something.test #{observability_url} 'self'") - end - end - - context 'when self is already present in the policy' do - let(:csp) do - ActionDispatch::ContentSecurityPolicy.new do |p| - p.frame_src "'self'" - end - end - - it 'does not append self again' do - expect(subject).to include( - "frame-src 'self' #{observability_url};") - end - end - - context 'when default-src exists in the CSP config' do - let(:csp) do - ActionDispatch::ContentSecurityPolicy.new do |p| - p.default_src 'https://something.test' - end - end - - it 'does not change default-src' do - expect(subject).to include( - "default-src https://something.test;") - end - - it 'appends the proper url to frame-src CSP directives' do - expect(subject).to include( - "frame-src https://something.test #{observability_url} 'self'") - end - end - - context 'when frame-src and default-src exist in the CSP config' do - let(:csp) do - ActionDispatch::ContentSecurityPolicy.new do |p| - p.default_src 'https://something_default.test' - p.frame_src 'https://something.test' - end - end - - it 'appends to frame-src CSP directives' do - expect(subject).to include( - "frame-src https://something.test #{observability_url} 'self'") - expect(subject).to include( - "default-src https://something_default.test") - end - end - end end diff --git a/spec/requests/groups/registry/repositories_controller_spec.rb b/spec/requests/groups/registry/repositories_controller_spec.rb index 0699f48c2be..f54acf118bb 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' -RSpec.describe Groups::Registry::RepositoriesController do +RSpec.describe Groups::Registry::RepositoriesController, feature_category: :container_registry do let_it_be(:group, reload: true) { create(:group) } let_it_be(:user) { create(:user) } diff --git a/spec/requests/groups/settings/access_tokens_controller_spec.rb b/spec/requests/groups/settings/access_tokens_controller_spec.rb index 6b150e0acb6..f26b69f8d30 100644 --- a/spec/requests/groups/settings/access_tokens_controller_spec.rb +++ b/spec/requests/groups/settings/access_tokens_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::Settings::AccessTokensController do +RSpec.describe Groups::Settings::AccessTokensController, feature_category: :authentication_and_authorization do let_it_be(:user) { create(:user) } let_it_be(:resource) { create(:group) } let_it_be(:access_token_user) { create(:user, :project_bot) } diff --git a/spec/requests/groups/settings/applications_controller_spec.rb b/spec/requests/groups/settings/applications_controller_spec.rb index 74313491414..fb91cd8bdab 100644 --- a/spec/requests/groups/settings/applications_controller_spec.rb +++ b/spec/requests/groups/settings/applications_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Groups::Settings::ApplicationsController do +RSpec.describe Groups::Settings::ApplicationsController, feature_category: :authentication_and_authorization do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:application) { create(:oauth_application, owner_id: group.id, owner_type: 'Namespace') } diff --git a/spec/requests/groups/usage_quotas_controller_spec.rb b/spec/requests/groups/usage_quotas_controller_spec.rb new file mode 100644 index 00000000000..bddc95434ce --- /dev/null +++ b/spec/requests/groups/usage_quotas_controller_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Groups::UsageQuotasController, feature_category: :subscription_cost_management do + let_it_be(:group) { create(:group) } + let_it_be(:subgroup) { create(:group, parent: group) } + let_it_be(:user) { create(:user) } + + subject(:request) { get group_usage_quotas_path(group) } + + before do + sign_in(user) + end + + describe 'GET /groups/*group_id/-/usage_quotas' do + context 'when user has read_usage_quotas permission' do + before do + group.add_owner(user) + end + + it 'renders index with 200 status code' do + request + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to match(/Placeholder for usage quotas Vue app/) + end + + it 'renders 404 page if subgroup' do + get group_usage_quotas_path(subgroup) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user does not have read_usage_quotas permission' do + before do + group.add_maintainer(user) + end + + it 'renders not_found' do + request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end +end diff --git a/spec/requests/groups_controller_spec.rb b/spec/requests/groups_controller_spec.rb index 422c108f2ad..7fc14910819 100644 --- a/spec/requests/groups_controller_spec.rb +++ b/spec/requests/groups_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe GroupsController do +RSpec.describe GroupsController, feature_category: :subgroups do context 'token authentication' do context 'when public group' do let_it_be(:public_group) { create(:group, :public) } diff --git a/spec/requests/health_controller_spec.rb b/spec/requests/health_controller_spec.rb index ae15b63df19..83ec1565095 100644 --- a/spec/requests/health_controller_spec.rb +++ b/spec/requests/health_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe HealthController do +RSpec.describe HealthController, feature_category: :database do include StubENV let(:token) { Gitlab::CurrentSettings.health_check_access_token } diff --git a/spec/requests/ide_controller_spec.rb b/spec/requests/ide_controller_spec.rb index 8d61399c824..b287ded799d 100644 --- a/spec/requests/ide_controller_spec.rb +++ b/spec/requests/ide_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe IdeController do +RSpec.describe IdeController, feature_category: :web_ide do using RSpec::Parameterized::TableSyntax let_it_be(:reporter) { create(:user) } @@ -21,7 +21,20 @@ RSpec.describe IdeController do let(:user) { creator } let(:branch) { '' } + def find_csp_frame_src + csp = response.headers['Content-Security-Policy'] + + # Transform "frame-src foo bar; connect-src foo bar; script-src ..." + # into array of connect-src values + csp.split(';') + .map(&:strip) + .find { |entry| entry.starts_with?('frame-src') } + .split(' ') + .drop(1) + end + before do + stub_feature_flags(vscode_web_ide: true) sign_in(user) end @@ -265,5 +278,17 @@ RSpec.describe IdeController do end end end + + describe 'frame-src content security policy' do + let(:route) { '/-/ide' } + + before do + subject + end + + it 'adds https://*.vscode-cdn.net in frame-src CSP policy' do + expect(find_csp_frame_src).to include("https://*.vscode-cdn.net/") + end + end end end diff --git a/spec/requests/import/github_groups_controller_spec.rb b/spec/requests/import/github_groups_controller_spec.rb index 544cbf88cd2..6393dd35a98 100644 --- a/spec/requests/import/github_groups_controller_spec.rb +++ b/spec/requests/import/github_groups_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::GithubGroupsController do +RSpec.describe Import::GithubGroupsController, feature_category: :importers do describe 'GET status' do subject(:status) { get '/import/github_group/status', params: params, headers: headers } diff --git a/spec/requests/import/gitlab_groups_controller_spec.rb b/spec/requests/import/gitlab_groups_controller_spec.rb index 8d5c1e3ebab..1766c48cca1 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' -RSpec.describe Import::GitlabGroupsController do +RSpec.describe Import::GitlabGroupsController, feature_category: :importers do include WorkhorseHelpers include_context 'workhorse headers' diff --git a/spec/requests/import/gitlab_projects_controller_spec.rb b/spec/requests/import/gitlab_projects_controller_spec.rb index eed035608d0..b2c2d306e53 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' -RSpec.describe Import::GitlabProjectsController do +RSpec.describe Import::GitlabProjectsController, feature_category: :importers do include WorkhorseHelpers include_context 'workhorse headers' diff --git a/spec/requests/import/url_controller_spec.rb b/spec/requests/import/url_controller_spec.rb index 63af5e8b469..fa2abda6711 100644 --- a/spec/requests/import/url_controller_spec.rb +++ b/spec/requests/import/url_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::UrlController do +RSpec.describe Import::UrlController, feature_category: :importers do let_it_be(:user) { create(:user) } before do diff --git a/spec/requests/jira_authorizations_spec.rb b/spec/requests/jira_authorizations_spec.rb index f67288b286b..8c27b61712c 100644 --- a/spec/requests/jira_authorizations_spec.rb +++ b/spec/requests/jira_authorizations_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Jira authorization requests' do +RSpec.describe 'Jira authorization requests', feature_category: :integrations do let(:user) { create :user } let(:application) { create :oauth_application, scopes: 'api' } let(:redirect_uri) { oauth_jira_dvcs_callback_url(host: "http://www.example.com") } diff --git a/spec/requests/jira_connect/cors_preflight_checks_controller_spec.rb b/spec/requests/jira_connect/cors_preflight_checks_controller_spec.rb deleted file mode 100644 index d441a8575d0..00000000000 --- a/spec/requests/jira_connect/cors_preflight_checks_controller_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe JiraConnect::CorsPreflightChecksController do - shared_examples 'allows cross-origin requests on self managed' do - it 'renders not found' do - options path - - expect(response).to have_gitlab_http_status(:not_found) - expect(response.headers['Access-Control-Allow-Origin']).to be_nil - end - - context 'with jira_connect_proxy_url setting' do - before do - stub_application_setting(jira_connect_proxy_url: 'https://gitlab.com') - - options path, headers: { 'Origin' => 'http://notgitlab.com' } - end - - it 'returns 200' do - expect(response).to have_gitlab_http_status(:ok) - end - - it 'responds with access-control-allow headers', :aggregate_failures do - expect(response.headers['Access-Control-Allow-Origin']).to eq 'https://gitlab.com' - expect(response.headers['Access-Control-Allow-Methods']).to eq allowed_methods - expect(response.headers['Access-Control-Allow-Credentials']).to be_nil - end - - context 'when on GitLab.com' do - before do - allow(Gitlab).to receive(:com?).and_return(true) - end - - it 'renders not found' do - options path - - expect(response).to have_gitlab_http_status(:not_found) - expect(response.headers['Access-Control-Allow-Origin']).to be_nil - end - end - end - end - - describe 'OPTIONS /-/jira_connect/oauth_application_id' do - let(:allowed_methods) { 'GET, OPTIONS' } - let(:path) { '/-/jira_connect/oauth_application_id' } - - it_behaves_like 'allows cross-origin requests on self managed' - end - - describe 'OPTIONS /-/jira_connect/subscriptions/:id' do - let(:allowed_methods) { 'DELETE, OPTIONS' } - let(:path) { '/-/jira_connect/subscriptions/123' } - - it_behaves_like 'allows cross-origin requests on self managed' - end -end diff --git a/spec/requests/jira_connect/installations_controller_spec.rb b/spec/requests/jira_connect/installations_controller_spec.rb index 6315c66a41a..67544bbca2e 100644 --- a/spec/requests/jira_connect/installations_controller_spec.rb +++ b/spec/requests/jira_connect/installations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraConnect::InstallationsController do +RSpec.describe JiraConnect::InstallationsController, feature_category: :integrations do let_it_be(:installation) { create(:jira_connect_installation) } describe 'GET /-/jira_connect/installations' do @@ -47,16 +47,18 @@ RSpec.describe JiraConnect::InstallationsController do end describe 'PUT /-/jira_connect/installations' do - before do + subject(:do_request) do put '/-/jira_connect/installations', params: { jwt: jwt, installation: { instance_url: update_instance_url } } end - let(:update_instance_url) { 'https://example.com' } + let(:update_instance_url) { nil } context 'without JWT' do let(:jwt) { nil } it 'returns 403' do + do_request + expect(response).to have_gitlab_http_status(:forbidden) end end @@ -66,28 +68,69 @@ RSpec.describe JiraConnect::InstallationsController do let(:jwt) { Atlassian::Jwt.encode({ iss: installation.client_key, qsh: qsh }, installation.shared_secret) } it 'returns 200' do + do_request + expect(response).to have_gitlab_http_status(:ok) end - it 'updates the instance_url' do - expect(json_response).to eq({ - 'gitlab_com' => false, - 'instance_url' => 'https://example.com' - }) - end + context 'with instance_url param' do + let(:update_instance_url) { 'https://example.com' } - context 'invalid URL' do - let(:update_instance_url) { 'invalid url' } + context 'instance response with success' do + before do + stub_request(:post, 'https://example.com/-/jira_connect/events/installed') + end - it 'returns 422 and errors', :aggregate_failures do - expect(response).to have_gitlab_http_status(:unprocessable_entity) - expect(json_response).to eq({ - 'errors' => { - 'instance_url' => [ - 'is blocked: Only allowed schemes are http, https' - ] - } - }) + it 'updates the instance_url' do + do_request + + expect(json_response).to eq({ + 'gitlab_com' => false, + 'instance_url' => 'https://example.com' + }) + end + + it 'sends an installed event to the self-managed instance' do + do_request + + expect(WebMock).to have_requested(:post, 'https://example.com/-/jira_connect/events/installed') + end + end + + context 'instance response with error' do + before do + stub_request(:post, 'https://example.com/-/jira_connect/events/installed').to_return(status: 422) + end + + it 'returns 422 and errors', :aggregate_failures do + do_request + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response).to eq({ + 'errors' => { + 'instance_url' => [ + 'Could not be installed on the instance. Error response code 422' + ] + } + }) + end + end + + context 'invalid URL' do + let(:update_instance_url) { 'invalid url' } + + it 'returns 422 and errors', :aggregate_failures do + do_request + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response).to eq({ + 'errors' => { + 'instance_url' => [ + 'is blocked: Only allowed schemes are http, https' + ] + } + }) + end end end end diff --git a/spec/requests/jira_connect/oauth_application_ids_controller_spec.rb b/spec/requests/jira_connect/oauth_application_ids_controller_spec.rb index 1d772e973ff..d111edd06da 100644 --- a/spec/requests/jira_connect/oauth_application_ids_controller_spec.rb +++ b/spec/requests/jira_connect/oauth_application_ids_controller_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' -RSpec.describe JiraConnect::OauthApplicationIdsController do +RSpec.describe JiraConnect::OauthApplicationIdsController, feature_category: :integrations do describe 'GET /-/jira_connect/oauth_application_id' do - let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } } + let(:cors_request_headers) { { 'Origin' => 'https://gitlab.com' } } before do stub_application_setting(jira_connect_application_key: '123456') @@ -38,9 +38,9 @@ RSpec.describe JiraConnect::OauthApplicationIdsController do end end - context 'when jira_connect_oauth_self_managed disabled' do + context 'on GitLab.com' do before do - stub_feature_flags(jira_connect_oauth_self_managed: false) + allow(Gitlab).to receive(:com?).and_return(true) end it 'renders not found' do @@ -49,17 +49,22 @@ RSpec.describe JiraConnect::OauthApplicationIdsController do expect(response).to have_gitlab_http_status(:not_found) end end + end - context 'on GitLab.com' do - before do - allow(Gitlab).to receive(:com?).and_return(true) - end + describe 'OPTIONS /-/jira_connect/oauth_application_id' do + let(:cors_request_headers) { { 'Origin' => 'https://gitlab.com', 'access-control-request-method' => 'GET' } } - it 'renders not found' do - get '/-/jira_connect/oauth_application_id' + before do + stub_application_setting(jira_connect_application_key: '123456') + stub_application_setting(jira_connect_proxy_url: 'https://gitlab.com') + end - expect(response).to have_gitlab_http_status(:not_found) - end + it 'allows cross-origin requests', :aggregate_failures do + options '/-/jira_connect/oauth_application_id', headers: cors_request_headers + + expect(response.headers['Access-Control-Allow-Origin']).to eq 'https://gitlab.com' + expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, OPTIONS' + expect(response.headers['Access-Control-Allow-Credentials']).to be_nil end end end diff --git a/spec/requests/jira_connect/oauth_callbacks_controller_spec.rb b/spec/requests/jira_connect/oauth_callbacks_controller_spec.rb index 12b9429b648..676b562c193 100644 --- a/spec/requests/jira_connect/oauth_callbacks_controller_spec.rb +++ b/spec/requests/jira_connect/oauth_callbacks_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraConnect::OauthCallbacksController do +RSpec.describe JiraConnect::OauthCallbacksController, feature_category: :integrations do describe 'GET /-/jira_connect/oauth_callbacks' do context 'when logged in' do it 'renders a page prompting the user to close the window' do diff --git a/spec/requests/jira_connect/public_keys_controller_spec.rb b/spec/requests/jira_connect/public_keys_controller_spec.rb index 2eca4c0ea2f..bf472469d85 100644 --- a/spec/requests/jira_connect/public_keys_controller_spec.rb +++ b/spec/requests/jira_connect/public_keys_controller_spec.rb @@ -2,15 +2,15 @@ require 'spec_helper' -RSpec.describe JiraConnect::PublicKeysController do +RSpec.describe JiraConnect::PublicKeysController, feature_category: :integrations do describe 'GET /-/jira_connect/public_keys/:uuid' do + let(:uuid) { non_existing_record_id } + let(:public_key_storage_enabled) { true } + before do - allow(Gitlab).to receive(:com?).and_return(dot_com) + allow(Gitlab.config.jira_connect).to receive(:enable_public_keys_storage).and_return(public_key_storage_enabled) end - let(:uuid) { non_existing_record_id } - let(:dot_com) { true } - it 'renders 404' do get jira_connect_public_key_path(id: uuid) @@ -29,8 +29,8 @@ RSpec.describe JiraConnect::PublicKeysController do expect(response.body).to eq(public_key.key) end - context 'when not on GitLab.com' do - let(:dot_com) { false } + context 'when public key storage disabled' do + let(:public_key_storage_enabled) { false } it 'renders 404' do get jira_connect_public_key_path(id: uuid) diff --git a/spec/requests/jira_connect/subscriptions_controller_spec.rb b/spec/requests/jira_connect/subscriptions_controller_spec.rb index b5f3ab916a4..8b019970b61 100644 --- a/spec/requests/jira_connect/subscriptions_controller_spec.rb +++ b/spec/requests/jira_connect/subscriptions_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraConnect::SubscriptionsController do +RSpec.describe JiraConnect::SubscriptionsController, feature_category: :integrations do describe 'GET /-/jira_connect/subscriptions' do let_it_be(:installation) { create(:jira_connect_installation, instance_url: 'http://self-managed-gitlab.com') } let(:qsh) do @@ -10,7 +10,7 @@ RSpec.describe JiraConnect::SubscriptionsController do end let(:jwt) { Atlassian::Jwt.encode({ iss: installation.client_key, qsh: qsh }, installation.shared_secret) } - let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } } + let(:cors_request_headers) { { 'Origin' => 'https://gitlab.com' } } let(:path) { '/-/jira_connect/subscriptions' } let(:params) { { jwt: jwt } } @@ -19,7 +19,7 @@ RSpec.describe JiraConnect::SubscriptionsController do end subject(:content_security_policy) do - get path, params: params + get path, params: params, headers: cors_request_headers response.headers['Content-Security-Policy'] end @@ -27,6 +27,17 @@ RSpec.describe JiraConnect::SubscriptionsController do it { is_expected.to include('http://self-managed-gitlab.com/-/jira_connect/') } it { is_expected.to include('http://self-managed-gitlab.com/api/') } it { is_expected.to include('http://self-managed-gitlab.com/oauth/') } + it { is_expected.to include('frame-ancestors \'self\' https://*.atlassian.net https://*.jira.com') } + + context 'with additional iframe ancestors' do + before do + allow(Gitlab.config.jira_connect).to receive(:additional_iframe_ancestors).and_return(['http://localhost:*', 'http://dev.gitlab.com']) + end + + it { + is_expected.to include('frame-ancestors \'self\' https://*.atlassian.net https://*.jira.com http://localhost:* http://dev.gitlab.com') + } + end context 'with no self-managed instance configured' do let_it_be(:installation) { create(:jira_connect_installation, instance_url: '') } @@ -36,14 +47,48 @@ RSpec.describe JiraConnect::SubscriptionsController do it { is_expected.not_to include('http://self-managed-gitlab.com/oauth/') } end - context 'with jira_connect_oauth_self_managed_setting feature disabled' do - before do - stub_feature_flags(jira_connect_oauth_self_managed_setting: false) + context 'when json format' do + let(:path) { '/-/jira_connect/subscriptions.json' } + + it 'allows cross-origin requests', :aggregate_failures do + get path, params: params, headers: cors_request_headers + + expect(response.headers['Access-Control-Allow-Origin']).to eq 'https://gitlab.com' + expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, OPTIONS' + expect(response.headers['Access-Control-Allow-Credentials']).to be_nil end + end + end - it { is_expected.not_to include('http://self-managed-gitlab.com/-/jira_connect/') } - it { is_expected.not_to include('http://self-managed-gitlab.com/api/') } - it { is_expected.not_to include('http://self-managed-gitlab.com/oauth/') } + describe 'OPTIONS /-/jira_connect/subscriptions' do + let(:cors_request_headers) { { 'Origin' => 'https://gitlab.com', 'access-control-request-method' => 'GET' } } + + before do + stub_application_setting(jira_connect_proxy_url: 'https://gitlab.com') + end + + it 'allows cross-origin requests', :aggregate_failures do + options '/-/jira_connect/subscriptions.json', headers: cors_request_headers + + expect(response.headers['Access-Control-Allow-Origin']).to eq 'https://gitlab.com' + expect(response.headers['Access-Control-Allow-Methods']).to eq 'GET, OPTIONS' + expect(response.headers['Access-Control-Allow-Credentials']).to be_nil + end + end + + describe 'OPTIONS /-/jira_connect/subscriptions/:id' do + let(:cors_request_headers) { { 'Origin' => 'https://gitlab.com', 'access-control-request-method' => 'DELETE' } } + + before do + stub_application_setting(jira_connect_proxy_url: 'https://gitlab.com') + end + + it 'allows cross-origin requests', :aggregate_failures do + options '/-/jira_connect/subscriptions/1', headers: cors_request_headers + + expect(response.headers['Access-Control-Allow-Origin']).to eq 'https://gitlab.com' + expect(response.headers['Access-Control-Allow-Methods']).to eq 'DELETE, OPTIONS' + expect(response.headers['Access-Control-Allow-Credentials']).to be_nil end end @@ -56,7 +101,7 @@ RSpec.describe JiraConnect::SubscriptionsController do end let(:jwt) { Atlassian::Jwt.encode({ iss: installation.client_key, qsh: qsh }, installation.shared_secret) } - let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } } + let(:cors_request_headers) { { 'Origin' => 'https://gitlab.com' } } let(:params) { { jwt: jwt, format: :json } } before do diff --git a/spec/requests/jira_connect/users_controller_spec.rb b/spec/requests/jira_connect/users_controller_spec.rb index 6e927aaba91..c02bd324708 100644 --- a/spec/requests/jira_connect/users_controller_spec.rb +++ b/spec/requests/jira_connect/users_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraConnect::UsersController do +RSpec.describe JiraConnect::UsersController, feature_category: :integrations do describe 'GET /-/jira_connect/users' do let_it_be(:user) { create(:user) } diff --git a/spec/requests/jira_routing_spec.rb b/spec/requests/jira_routing_spec.rb index e0e170044de..f1edb58bb10 100644 --- a/spec/requests/jira_routing_spec.rb +++ b/spec/requests/jira_routing_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Jira referenced paths', type: :request do +RSpec.describe 'Jira referenced paths', type: :request, feature_category: :integrations do using RSpec::Parameterized::TableSyntax let(:user) { create(:user) } diff --git a/spec/requests/jwks_controller_spec.rb b/spec/requests/jwks_controller_spec.rb index c9dcc238c29..ac9765c35d8 100644 --- a/spec/requests/jwks_controller_spec.rb +++ b/spec/requests/jwks_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JwksController do +RSpec.describe JwksController, feature_category: :authentication_and_authorization do describe 'Endpoints from the parent Doorkeeper::OpenidConnect::DiscoveryController' do it 'respond successfully' do [ diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index e6916e02fde..00222cb1977 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JwtController do +RSpec.describe JwtController, feature_category: :authentication_and_authorization 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 3529239a4d9..e5f03d16dda 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' -RSpec.describe 'Git LFS API and storage' do +RSpec.describe 'Git LFS API and storage', feature_category: :source_code_management do using RSpec::Parameterized::TableSyntax include LfsHttpHelpers @@ -378,6 +378,21 @@ RSpec.describe 'Git LFS API and storage' do it_behaves_like 'LFS http 401 response' end + context 'when deploy token is from an unrelated group to the project' do + let(:group) { create(:group) } + let(:deploy_token) { create(:deploy_token, :group, groups: [group]) } + + it_behaves_like 'LFS http 401 response' + end + + context 'when deploy token is from a parent group of the project and valid' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:deploy_token) { create(:deploy_token, :group, groups: [group]) } + + it_behaves_like 'an authorized request', renew_authorization: false + end + # TODO: We should fix this test case that causes flakyness by alternating the result of the above test cases. context 'when Deploy Token is valid' do let(:deploy_token) { create(:deploy_token, projects: [project]) } @@ -842,14 +857,6 @@ RSpec.describe 'Git LFS API and storage' do lfs_object.destroy! end - context 'with object storage disabled' do - it "doesn't attempt to migrate file to object storage" do - expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async) - - put_finalize(with_tempfile: true) - end - end - context 'with object storage enabled' do context 'and direct upload enabled' do let!(:fog_connection) do @@ -911,18 +918,6 @@ RSpec.describe 'Git LFS API and storage' do end end end - - context 'and background upload enabled' do - before do - stub_lfs_object_storage(background_upload: true) - end - - it 'schedules migration of file to object storage' do - expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('LfsObjectUploader', 'LfsObject', :file, kind_of(Numeric)) - - put_finalize(with_tempfile: true) - end - end end end @@ -1036,7 +1031,7 @@ RSpec.describe 'Git LFS API and storage' do end describe 'to a forked project' do - let_it_be(:upstream_project) { create(:project, :public) } + let_it_be_with_reload(:upstream_project) { create(:project, :public) } let_it_be(:project_owner) { create(:user) } let(:project) { fork_project(upstream_project, project_owner) } @@ -1074,6 +1069,56 @@ RSpec.describe 'Git LFS API and storage' do end end + describe 'when user has push access to upstream project' do + before do + upstream_project.add_maintainer(user) + end + + context 'an MR exists on target forked project' do + let(:allow_collaboration) { true } + let(:merge_request) do + create(:merge_request, + target_project: upstream_project, + source_project: project, + allow_collaboration: allow_collaboration) + end + + before do + merge_request + end + + context 'with allow_collaboration option set to true' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + put_authorize + end + + it_behaves_like 'LFS http 200 workhorse response' + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end + + it_behaves_like 'LFS http 200 response' + end + end + + context 'with allow_collaboration option set to false' do + context 'request is sent by gitlab-workhorse to authorize the request' do + let(:allow_collaboration) { false } + + before do + put_authorize + end + + it_behaves_like 'forbidden' + end + end + end + end + describe 'and user does not have push access' do it_behaves_like 'forbidden' end diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb index 0eb3cb4ca07..363a16f014b 100644 --- a/spec/requests/lfs_locks_api_spec.rb +++ b/spec/requests/lfs_locks_api_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Git LFS File Locking API' do +RSpec.describe 'Git LFS File Locking API', feature_category: :source_code_management do include LfsHttpHelpers include WorkhorseHelpers diff --git a/spec/requests/mailgun/webhooks_controller_spec.rb b/spec/requests/mailgun/webhooks_controller_spec.rb index ae6dc89d003..669401242b2 100644 --- a/spec/requests/mailgun/webhooks_controller_spec.rb +++ b/spec/requests/mailgun/webhooks_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mailgun::WebhooksController do +RSpec.describe Mailgun::WebhooksController, feature_category: :team_planning do let(:mailgun_signing_key) { 'abc123' } let(:valid_signature) do { diff --git a/spec/requests/oauth/applications_controller_spec.rb b/spec/requests/oauth/applications_controller_spec.rb index 78f0cedb56f..94ee08f6272 100644 --- a/spec/requests/oauth/applications_controller_spec.rb +++ b/spec/requests/oauth/applications_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Oauth::ApplicationsController do +RSpec.describe Oauth::ApplicationsController, feature_category: :authentication_and_authorization do let_it_be(:user) { create(:user) } let_it_be(:application) { create(:oauth_application, owner: user) } let_it_be(:show_path) { oauth_application_path(application) } diff --git a/spec/requests/oauth/authorizations_controller_spec.rb b/spec/requests/oauth/authorizations_controller_spec.rb index 8d19c92865e..52188717210 100644 --- a/spec/requests/oauth/authorizations_controller_spec.rb +++ b/spec/requests/oauth/authorizations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Oauth::AuthorizationsController do +RSpec.describe Oauth::AuthorizationsController, feature_category: :authentication_and_authorization do let_it_be(:user) { create(:user) } let_it_be(:application) { create(:oauth_application, redirect_uri: 'custom://test') } let_it_be(:oauth_authorization_path) do diff --git a/spec/requests/oauth/tokens_controller_spec.rb b/spec/requests/oauth/tokens_controller_spec.rb index 507489d92cf..cdfad8cb59c 100644 --- a/spec/requests/oauth/tokens_controller_spec.rb +++ b/spec/requests/oauth/tokens_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Oauth::TokensController do +RSpec.describe Oauth::TokensController, feature_category: :authentication_and_authorization do let(:cors_request_headers) { { 'Origin' => 'http://notgitlab.com' } } let(:other_headers) { {} } let(:headers) { cors_request_headers.merge(other_headers) } diff --git a/spec/requests/oauth_tokens_spec.rb b/spec/requests/oauth_tokens_spec.rb index f2fb380bde0..053bd317fcc 100644 --- a/spec/requests/oauth_tokens_spec.rb +++ b/spec/requests/oauth_tokens_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'OAuth Tokens requests' do +RSpec.describe 'OAuth Tokens requests', feature_category: :authentication_and_authorization do let(:user) { create :user } let(:application) { create :oauth_application, scopes: 'api' } let(:grant_type) { 'authorization_code' } diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index 3a40fec58e8..b45f4f1e39f 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'OpenID Connect requests' do +RSpec.describe 'OpenID Connect requests', feature_category: :authentication_and_authorization do let(:user) do create( :user, diff --git a/spec/requests/profiles/notifications_controller_spec.rb b/spec/requests/profiles/notifications_controller_spec.rb index d7dfb1c675d..21e166e04d3 100644 --- a/spec/requests/profiles/notifications_controller_spec.rb +++ b/spec/requests/profiles/notifications_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'view user notifications' do +RSpec.describe 'view user notifications', feature_category: :team_planning 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/ci/promeheus_metrics/histograms_controller_spec.rb b/spec/requests/projects/ci/promeheus_metrics/histograms_controller_spec.rb index c5e7369b0a9..b0c7427fa81 100644 --- a/spec/requests/projects/ci/promeheus_metrics/histograms_controller_spec.rb +++ b/spec/requests/projects/ci/promeheus_metrics/histograms_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Projects::Ci::PrometheusMetrics::HistogramsController' do +RSpec.describe 'Projects::Ci::PrometheusMetrics::HistogramsController', feature_category: :pipeline_authoring do let_it_be(:project) { create(:project, :public) } describe 'POST /*namespace_id/:project_id/-/ci/prometheus_metrics/histograms' do diff --git a/spec/requests/projects/cluster_agents_controller_spec.rb b/spec/requests/projects/cluster_agents_controller_spec.rb index 914d5b17ba8..d7c791fa0c1 100644 --- a/spec/requests/projects/cluster_agents_controller_spec.rb +++ b/spec/requests/projects/cluster_agents_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::ClusterAgentsController do +RSpec.describe Projects::ClusterAgentsController, feature_category: :kubernetes_management do let_it_be(:cluster_agent) { create(:cluster_agent) } let(:project) { cluster_agent.project } diff --git a/spec/requests/projects/clusters/integrations_controller_spec.rb b/spec/requests/projects/clusters/integrations_controller_spec.rb index c05e3da675c..505b63e1ff6 100644 --- a/spec/requests/projects/clusters/integrations_controller_spec.rb +++ b/spec/requests/projects/clusters/integrations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Clusters::IntegrationsController do +RSpec.describe Projects::Clusters::IntegrationsController, feature_category: :integrations do include AccessMatchersForController shared_examples 'a secure endpoint' do diff --git a/spec/requests/projects/commits_controller_spec.rb b/spec/requests/projects/commits_controller_spec.rb index 158902c0ffd..f9e3ef82fc1 100644 --- a/spec/requests/projects/commits_controller_spec.rb +++ b/spec/requests/projects/commits_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::CommitsController do +RSpec.describe Projects::CommitsController, feature_category: :source_code_management do context 'token authentication' do context 'when public project' do let_it_be(:public_project) { create(:project, :repository, :public) } diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 370febf82ff..3f9dd74c145 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' -RSpec.describe 'value stream analytics events' do +RSpec.describe 'value stream analytics events', feature_category: :planning_analytics do include CycleAnalyticsHelpers let(:user) { create(:user) } diff --git a/spec/requests/projects/environments_controller_spec.rb b/spec/requests/projects/environments_controller_spec.rb index 66ab265fc0f..41ae2d434fa 100644 --- a/spec/requests/projects/environments_controller_spec.rb +++ b/spec/requests/projects/environments_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::EnvironmentsController do +RSpec.describe Projects::EnvironmentsController, feature_category: :continuous_delivery do let_it_be_with_refind(:project) { create(:project, :repository) } let(:environment) { create(:environment, name: 'production', project: project) } diff --git a/spec/requests/projects/google_cloud/configuration_controller_spec.rb b/spec/requests/projects/google_cloud/configuration_controller_spec.rb index 41593b8d7a7..1aa44d1a49a 100644 --- a/spec/requests/projects/google_cloud/configuration_controller_spec.rb +++ b/spec/requests/projects/google_cloud/configuration_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GoogleCloud::ConfigurationController do +RSpec.describe Projects::GoogleCloud::ConfigurationController, feature_category: :kubernetes_management do let_it_be(:project) { create(:project, :public) } let_it_be(:url) { project_google_cloud_configuration_path(project) } diff --git a/spec/requests/projects/google_cloud/databases_controller_spec.rb b/spec/requests/projects/google_cloud/databases_controller_spec.rb index 4edef71f326..e91a51ce2ef 100644 --- a/spec/requests/projects/google_cloud/databases_controller_spec.rb +++ b/spec/requests/projects/google_cloud/databases_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GoogleCloud::DatabasesController, :snowplow do +RSpec.describe Projects::GoogleCloud::DatabasesController, :snowplow, feature_category: :kubernetes_management do shared_examples 'shared examples for database controller endpoints' do include_examples 'requires `admin_project_google_cloud` role' diff --git a/spec/requests/projects/google_cloud/deployments_controller_spec.rb b/spec/requests/projects/google_cloud/deployments_controller_spec.rb index c777e8c1f69..d564a31f835 100644 --- a/spec/requests/projects/google_cloud/deployments_controller_spec.rb +++ b/spec/requests/projects/google_cloud/deployments_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GoogleCloud::DeploymentsController do +RSpec.describe Projects::GoogleCloud::DeploymentsController, feature_category: :kubernetes_management do let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:repository) { project.repository } diff --git a/spec/requests/projects/google_cloud/gcp_regions_controller_spec.rb b/spec/requests/projects/google_cloud/gcp_regions_controller_spec.rb index e77bcdb40b8..de4b96a2e01 100644 --- a/spec/requests/projects/google_cloud/gcp_regions_controller_spec.rb +++ b/spec/requests/projects/google_cloud/gcp_regions_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GoogleCloud::GcpRegionsController do +RSpec.describe Projects::GoogleCloud::GcpRegionsController, feature_category: :kubernetes_management do let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:repository) { project.repository } diff --git a/spec/requests/projects/google_cloud/revoke_oauth_controller_spec.rb b/spec/requests/projects/google_cloud/revoke_oauth_controller_spec.rb index 9bd8468767d..5965953cf6f 100644 --- a/spec/requests/projects/google_cloud/revoke_oauth_controller_spec.rb +++ b/spec/requests/projects/google_cloud/revoke_oauth_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GoogleCloud::RevokeOauthController do +RSpec.describe Projects::GoogleCloud::RevokeOauthController, feature_category: :kubernetes_management do include SessionHelpers describe 'POST #create', :snowplow, :clean_gitlab_redis_sessions, :aggregate_failures do diff --git a/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb b/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb index d91e5a4f068..9b048f814ef 100644 --- a/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb +++ b/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::GoogleCloud::ServiceAccountsController do +RSpec.describe Projects::GoogleCloud::ServiceAccountsController, feature_category: :kubernetes_management do let_it_be(:project) { create(:project, :public) } describe 'GET index', :snowplow do diff --git a/spec/requests/projects/harbor/artifacts_controller_spec.rb b/spec/requests/projects/harbor/artifacts_controller_spec.rb index 310fbcf0a0f..d0ed93acaf7 100644 --- a/spec/requests/projects/harbor/artifacts_controller_spec.rb +++ b/spec/requests/projects/harbor/artifacts_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Harbor::ArtifactsController do +RSpec.describe Projects::Harbor::ArtifactsController, feature_category: :build_artifacts do it_behaves_like 'a harbor artifacts controller', anonymous_status_code: '302' do let_it_be(:container) { create(:project) } let_it_be(:harbor_integration) { create(:harbor_integration, project: container) } diff --git a/spec/requests/projects/harbor/repositories_controller_spec.rb b/spec/requests/projects/harbor/repositories_controller_spec.rb index 751becaa20a..7430ac5a64f 100644 --- a/spec/requests/projects/harbor/repositories_controller_spec.rb +++ b/spec/requests/projects/harbor/repositories_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Harbor::RepositoriesController do +RSpec.describe Projects::Harbor::RepositoriesController, feature_category: :source_code_management do it_behaves_like 'a harbor repositories controller', anonymous_status_code: '302' do let_it_be(:container, reload: true) { create(:project) } let_it_be(:harbor_integration) { create(:harbor_integration, project: container) } diff --git a/spec/requests/projects/harbor/tags_controller_spec.rb b/spec/requests/projects/harbor/tags_controller_spec.rb index 119d1c746ac..f1ac2f01c57 100644 --- a/spec/requests/projects/harbor/tags_controller_spec.rb +++ b/spec/requests/projects/harbor/tags_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Harbor::TagsController do +RSpec.describe Projects::Harbor::TagsController, feature_category: :source_code_management do it_behaves_like 'a harbor tags controller', anonymous_status_code: '302' do let_it_be(:container) { create(:project) } let_it_be(:harbor_integration) { create(:harbor_integration, project: container) } diff --git a/spec/requests/projects/hook_logs_controller_spec.rb b/spec/requests/projects/hook_logs_controller_spec.rb index 8b3ec307e53..c71906b4895 100644 --- a/spec/requests/projects/hook_logs_controller_spec.rb +++ b/spec/requests/projects/hook_logs_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::HookLogsController do +RSpec.describe Projects::HookLogsController, feature_category: :integrations do let_it_be(:user) { create(:user) } let_it_be_with_refind(:web_hook) { create(:project_hook) } let_it_be_with_refind(:web_hook_log) { create(:web_hook_log, web_hook: web_hook) } diff --git a/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb index 32434435475..a25dcb7f299 100644 --- a/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb +++ b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'PagerDuty webhook' do +RSpec.describe 'PagerDuty webhook', feature_category: :incident_management do let_it_be(:project) { create(:project) } describe 'POST /incidents/pagerduty' do diff --git a/spec/requests/projects/incident_management/timeline_events_spec.rb b/spec/requests/projects/incident_management/timeline_events_spec.rb index f7dead4834d..22a1f654ee2 100644 --- a/spec/requests/projects/incident_management/timeline_events_spec.rb +++ b/spec/requests/projects/incident_management/timeline_events_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Timeline Events' do +RSpec.describe 'Timeline Events', feature_category: :incident_management do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:incident) { create(:incident, project: project) } diff --git a/spec/requests/projects/integrations/shimos_controller_spec.rb b/spec/requests/projects/integrations/shimos_controller_spec.rb index 7322143f87e..bd7af0bb4ac 100644 --- a/spec/requests/projects/integrations/shimos_controller_spec.rb +++ b/spec/requests/projects/integrations/shimos_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Projects::Integrations::ShimosController do +RSpec.describe ::Projects::Integrations::ShimosController, feature_category: :integrations do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user, developer_projects: [project]) } let_it_be(:shimo_integration) { create(:shimo_integration, project: project) } diff --git a/spec/requests/projects/issue_links_controller_spec.rb b/spec/requests/projects/issue_links_controller_spec.rb index e5f40625cfa..0535156b4b8 100644 --- a/spec/requests/projects/issue_links_controller_spec.rb +++ b/spec/requests/projects/issue_links_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::IssueLinksController do +RSpec.describe Projects::IssueLinksController, feature_category: :team_planning do let(:user) { create :user } let(:project) { create(:project_empty_repo) } let(:issue) { create :issue, project: project } diff --git a/spec/requests/projects/issues/discussions_spec.rb b/spec/requests/projects/issues/discussions_spec.rb index dcdca2d9c27..0f4a0bd2e5c 100644 --- a/spec/requests/projects/issues/discussions_spec.rb +++ b/spec/requests/projects/issues/discussions_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'issue discussions' do +RSpec.describe 'issue discussions', feature_category: :team_planning do describe 'GET /:namespace/:project/-/issues/:iid/discussions' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/requests/projects/issues_controller_spec.rb b/spec/requests/projects/issues_controller_spec.rb index aa2ba5e114b..bbf200eaacd 100644 --- a/spec/requests/projects/issues_controller_spec.rb +++ b/spec/requests/projects/issues_controller_spec.rb @@ -2,12 +2,36 @@ require 'spec_helper' -RSpec.describe Projects::IssuesController do +RSpec.describe Projects::IssuesController, feature_category: :team_planning do let_it_be(:issue) { create(:issue) } let_it_be(:group) { create(:group) } let_it_be(:project) { issue.project } let_it_be(:user) { issue.author } + describe 'GET #new' do + before do + login_as(user) + end + + it_behaves_like "observability csp policy", described_class do + let(:tested_path) do + new_project_issue_path(project) + end + end + end + + describe 'GET #show' do + before do + login_as(user) + end + + it_behaves_like "observability csp policy", described_class do + let(:tested_path) do + project_issue_path(project, issue) + end + end + end + describe 'GET #discussions' do before do login_as(user) diff --git a/spec/requests/projects/merge_requests/content_spec.rb b/spec/requests/projects/merge_requests/content_spec.rb index 7e5ec6f64c4..6c58dcb5722 100644 --- a/spec/requests/projects/merge_requests/content_spec.rb +++ b/spec/requests/projects/merge_requests/content_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'merge request content spec' do +RSpec.describe 'merge request content spec', feature_category: :code_review do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } let_it_be(:merge_request) { create(:merge_request, :with_head_pipeline, target_project: project, source_project: project) } diff --git a/spec/requests/projects/merge_requests/context_commit_diffs_spec.rb b/spec/requests/projects/merge_requests/context_commit_diffs_spec.rb index ec65e8cf11e..10e57970704 100644 --- a/spec/requests/projects/merge_requests/context_commit_diffs_spec.rb +++ b/spec/requests/projects/merge_requests/context_commit_diffs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Merge Requests Context Commit Diffs' do +RSpec.describe 'Merge Requests Context Commit Diffs', feature_category: :code_review do let_it_be(:sha1) { "33f3729a45c02fc67d00adb1b8bca394b0e761d9" } let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/requests/projects/merge_requests/creations_spec.rb b/spec/requests/projects/merge_requests/creations_spec.rb index 842ad01656e..59e2047e1c7 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' -RSpec.describe 'merge requests creations' do +RSpec.describe 'merge requests creations', feature_category: :code_review do describe 'GET /:namespace/:project/merge_requests/new' do include ProjectForksHelper @@ -24,5 +24,17 @@ RSpec.describe 'merge requests creations' do expect { get_new }.not_to exceed_query_limit(control) end + + it_behaves_like "observability csp policy", Projects::MergeRequests::CreationsController do + let(:tested_path) do + project_new_merge_request_path(project, merge_request: { + title: 'Some feature', + source_branch: 'fix', + target_branch: 'feature', + target_project: project, + source_project: project + }) + end + end end end diff --git a/spec/requests/projects/merge_requests/diffs_spec.rb b/spec/requests/projects/merge_requests/diffs_spec.rb index 12990b54617..858acac7f0d 100644 --- a/spec/requests/projects/merge_requests/diffs_spec.rb +++ b/spec/requests/projects/merge_requests/diffs_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Merge Requests Diffs' do +RSpec.describe 'Merge Requests Diffs', feature_category: :code_review do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } let_it_be(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } @@ -80,20 +80,6 @@ RSpec.describe 'Merge Requests Diffs' do expect(response).to have_gitlab_http_status(:not_modified) end - context 'with check_etags_diffs_batch_before_write_cache flag turned off' do - before do - stub_feature_flags(check_etags_diffs_batch_before_write_cache: false) - end - - it 'does not serialize diffs' do - expect(PaginatedDiffSerializer).not_to receive(:new) - - go(headers: headers, page: 0, per_page: 5) - - expect(response).to have_gitlab_http_status(:not_modified) - end - end - context 'with the different user' do let(:another_user) { create(:user) } let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch } diff --git a/spec/requests/projects/merge_requests_controller_spec.rb b/spec/requests/projects/merge_requests_controller_spec.rb index 2ee86bb423b..f5f8b5c2d83 100644 --- a/spec/requests/projects/merge_requests_controller_spec.rb +++ b/spec/requests/projects/merge_requests_controller_spec.rb @@ -2,11 +2,24 @@ require 'spec_helper' -RSpec.describe Projects::MergeRequestsController do +RSpec.describe Projects::MergeRequestsController, feature_category: :source_code_management do + let_it_be(:merge_request) { create(:merge_request) } + let_it_be(:project) { merge_request.project } + let_it_be(:user) { merge_request.author } + + describe 'GET #show' do + before do + login_as(user) + end + + it_behaves_like "observability csp policy", described_class do + let(:tested_path) do + project_merge_request_path(project, merge_request) + end + end + end + describe 'GET #discussions' do - let_it_be(:merge_request) { create(:merge_request) } - let_it_be(:project) { merge_request.project } - let_it_be(:user) { merge_request.author } let_it_be(:discussion) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) } let_it_be(:discussion_reply) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, in_reply_to: discussion) } let_it_be(:state_event) { create(:resource_state_event, merge_request: merge_request) } diff --git a/spec/requests/projects/merge_requests_discussions_spec.rb b/spec/requests/projects/merge_requests_discussions_spec.rb index 305ca6147be..d82fa284a42 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' -RSpec.describe 'merge requests discussions' do +RSpec.describe 'merge requests discussions', feature_category: :source_code_management 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, :public) } diff --git a/spec/requests/projects/merge_requests_spec.rb b/spec/requests/projects/merge_requests_spec.rb index 91153554e55..9600d1a3656 100644 --- a/spec/requests/projects/merge_requests_spec.rb +++ b/spec/requests/projects/merge_requests_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'merge requests actions' do +RSpec.describe 'merge requests actions', feature_category: :source_code_management do let_it_be(:project) { create(:project, :repository) } let(:merge_request) do diff --git a/spec/requests/projects/metrics/dashboards/builder_spec.rb b/spec/requests/projects/metrics/dashboards/builder_spec.rb index 002acca2135..c929beaed70 100644 --- a/spec/requests/projects/metrics/dashboards/builder_spec.rb +++ b/spec/requests/projects/metrics/dashboards/builder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Projects::Metrics::Dashboards::BuilderController' do +RSpec.describe 'Projects::Metrics::Dashboards::BuilderController', feature_category: :metrics do let_it_be(:project) { create(:project) } let_it_be(:environment) { create(:environment, project: project) } let_it_be(:user) { create(:user) } diff --git a/spec/requests/projects/metrics_dashboard_spec.rb b/spec/requests/projects/metrics_dashboard_spec.rb index 61bfe1c6edf..01925f8345b 100644 --- a/spec/requests/projects/metrics_dashboard_spec.rb +++ b/spec/requests/projects/metrics_dashboard_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Projects::MetricsDashboardController' do +RSpec.describe 'Projects::MetricsDashboardController', feature_category: :metrics do let_it_be(:project) { create(:project) } let_it_be(:environment) { create(:environment, project: project) } let_it_be(:environment2) { create(:environment, project: project) } diff --git a/spec/requests/projects/ml/candidates_controller_spec.rb b/spec/requests/projects/ml/candidates_controller_spec.rb new file mode 100644 index 00000000000..4a0fd1ce4f5 --- /dev/null +++ b/spec/requests/projects/ml/candidates_controller_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::Ml::CandidatesController, feature_category: :mlops do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.first_owner } + let_it_be(:experiment) { create(:ml_experiments, project: project, user: user) } + let_it_be(:candidate) { create(:ml_candidates, experiment: experiment, user: user) } + + let(:ff_value) { true } + let(:threshold) { 4 } + let(:candidate_iid) { candidate.iid } + + before do + stub_feature_flags(ml_experiment_tracking: false) + stub_feature_flags(ml_experiment_tracking: project) if ff_value + + sign_in(user) + end + + shared_examples '404 if feature flag disabled' do + context 'when :ml_experiment_tracking disabled' do + let(:ff_value) { false } + + it 'is 404' do + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'GET show' do + let(:params) { basic_params.merge(id: experiment.iid) } + + before do + show_candidate + end + + it 'renders the template' do + expect(response).to render_template('projects/ml/candidates/show') + end + + # MR removing this xit https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104166 + xit 'does not perform N+1 sql queries' do + control_count = ActiveRecord::QueryRecorder.new { show_candidate } + + create_list(:ml_candidate_params, 3, candidate: candidate) + create_list(:ml_candidate_metrics, 3, candidate: candidate) + + expect { show_candidate }.not_to exceed_all_query_limit(control_count).with_threshold(threshold) + end + + context 'when candidate does not exist' do + let(:candidate_iid) { non_existing_record_id.to_s } + + it 'returns 404' do + expect(response).to have_gitlab_http_status(:not_found) + end + end + + it_behaves_like '404 if feature flag disabled' + end + + private + + def show_candidate + get project_ml_candidate_path(project, candidate_iid) + end +end diff --git a/spec/requests/projects/ml/experiments_controller_spec.rb b/spec/requests/projects/ml/experiments_controller_spec.rb index 67a2fe47dc8..f35f93b1e6c 100644 --- a/spec/requests/projects/ml/experiments_controller_spec.rb +++ b/spec/requests/projects/ml/experiments_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Ml::ExperimentsController do +RSpec.describe Projects::Ml::ExperimentsController, feature_category: :mlops do let_it_be(:project_with_feature) { create(:project, :repository) } let_it_be(:user) { project_with_feature.first_owner } let_it_be(:project_without_feature) do @@ -17,7 +17,6 @@ RSpec.describe Projects::Ml::ExperimentsController do let(:params) { basic_params } let(:ff_value) { true } - let(:threshold) { 4 } let(:project) { project_with_feature } let(:basic_params) { { namespace_id: project.namespace.to_param, project_id: project } } @@ -48,11 +47,11 @@ RSpec.describe Projects::Ml::ExperimentsController do end it 'does not perform N+1 sql queries' do - control_count = ActiveRecord::QueryRecorder.new { list_experiments } + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { list_experiments } create_list(:ml_experiments, 2, project: project, user: user) - expect { list_experiments }.not_to exceed_all_query_limit(control_count).with_threshold(threshold) + expect { list_experiments }.not_to exceed_all_query_limit(control_count) end context 'when :ml_experiment_tracking is disabled for the project' do @@ -77,7 +76,8 @@ RSpec.describe Projects::Ml::ExperimentsController do expect(response).to render_template('projects/ml/experiments/show') end - it 'does not perform N+1 sql queries' do + # MR removing this xit https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104166 + xit 'does not perform N+1 sql queries' do control_count = ActiveRecord::QueryRecorder.new { show_experiment } create_list(:ml_candidates, 2, :with_metrics_and_params, experiment: experiment) diff --git a/spec/requests/projects/network_controller_spec.rb b/spec/requests/projects/network_controller_spec.rb new file mode 100644 index 00000000000..954f9655558 --- /dev/null +++ b/spec/requests/projects/network_controller_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::NetworkController, feature_category: :source_code_management do + let_it_be(:project) { create(:project, :repository, :private) } + let(:ref) { 'master' } + + describe 'GET #show' do + subject { get project_network_path(project, ref) } + + context 'when user is unauthorized' do + it 'shows 404' do + subject + expect(response).to redirect_to(new_user_session_path) + end + end + + context 'when user is authorized' do + let(:user) { project.creator } + + before do + sign_in(user) + end + + it 'renders content' do + subject + expect(response).to be_successful + end + + context 'when ref_type is provided' do + subject { get project_network_path(project, ref, ref_type: 'heads') } + + it 'assigns url with ref_type' do + subject + expect(assigns(:url)).to eq(project_network_path(project, ref, format: :json, ref_type: 'heads')) + end + + context 'when the use_ref_type_parameter flag is disabled' do + before do + stub_feature_flags(use_ref_type_parameter: false) + end + + it 'assigns url without ref_type' do + subject + expect(assigns(:url)).to eq(project_network_path(project, ref, format: :json)) + end + end + end + + it 'assigns url' do + subject + expect(assigns(:url)).to eq(project_network_path(project, ref, format: :json)) + end + end + end +end diff --git a/spec/requests/projects/noteable_notes_spec.rb b/spec/requests/projects/noteable_notes_spec.rb index 44ee50ca002..5699bf17b80 100644 --- a/spec/requests/projects/noteable_notes_spec.rb +++ b/spec/requests/projects/noteable_notes_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project noteable notes' do +RSpec.describe 'Project noteable notes', feature_category: :team_planning do describe '#index' do let_it_be(:merge_request) { create(:merge_request) } diff --git a/spec/requests/projects/packages/package_files_controller_spec.rb b/spec/requests/projects/packages/package_files_controller_spec.rb index a6daf57f0fa..e5849be9f13 100644 --- a/spec/requests/projects/packages/package_files_controller_spec.rb +++ b/spec/requests/projects/packages/package_files_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Packages::PackageFilesController do +RSpec.describe Projects::Packages::PackageFilesController, feature_category: :package_registry do let_it_be(:project) { create(:project, :public) } let_it_be(:package) { create(:package, project: project) } let_it_be(:package_file) { create(:package_file, package: package) } diff --git a/spec/requests/projects/pipelines_controller_spec.rb b/spec/requests/projects/pipelines_controller_spec.rb index 1c6b1039aee..7f185ade339 100644 --- a/spec/requests/projects/pipelines_controller_spec.rb +++ b/spec/requests/projects/pipelines_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::PipelinesController do +RSpec.describe Projects::PipelinesController, feature_category: :continuous_integration do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :repository) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/requests/projects/redirect_controller_spec.rb b/spec/requests/projects/redirect_controller_spec.rb index 3bbca3ca32b..e828c546198 100644 --- a/spec/requests/projects/redirect_controller_spec.rb +++ b/spec/requests/projects/redirect_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Projects::RedirectController requests" do +RSpec.describe "Projects::RedirectController requests", feature_category: :projects do using RSpec::Parameterized::TableSyntax let_it_be(:private_project) { create(:project, :private) } diff --git a/spec/requests/projects/releases_controller_spec.rb b/spec/requests/projects/releases_controller_spec.rb index 8e492125ace..d331142583d 100644 --- a/spec/requests/projects/releases_controller_spec.rb +++ b/spec/requests/projects/releases_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Projects::ReleasesController' do +RSpec.describe 'Projects::ReleasesController', feature_category: :release_orchestration do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } diff --git a/spec/requests/projects/settings/access_tokens_controller_spec.rb b/spec/requests/projects/settings/access_tokens_controller_spec.rb index 17389cdcce7..defb35fd496 100644 --- a/spec/requests/projects/settings/access_tokens_controller_spec.rb +++ b/spec/requests/projects/settings/access_tokens_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Settings::AccessTokensController do +RSpec.describe Projects::Settings::AccessTokensController, feature_category: :authentication_and_authorization do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:resource) { create(:project, group: group) } diff --git a/spec/requests/projects/settings/integration_hook_logs_controller_spec.rb b/spec/requests/projects/settings/integration_hook_logs_controller_spec.rb index 77daff901a1..6cd0df19468 100644 --- a/spec/requests/projects/settings/integration_hook_logs_controller_spec.rb +++ b/spec/requests/projects/settings/integration_hook_logs_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Settings::IntegrationHookLogsController do +RSpec.describe Projects::Settings::IntegrationHookLogsController, feature_category: :integrations do let_it_be(:user) { create(:user) } let_it_be(:integration) { create(:datadog_integration) } let_it_be_with_refind(:web_hook) { integration.service_hook } diff --git a/spec/requests/projects/settings/packages_and_registries_controller_spec.rb b/spec/requests/projects/settings/packages_and_registries_controller_spec.rb index 6d8a152c769..2806beadd4e 100644 --- a/spec/requests/projects/settings/packages_and_registries_controller_spec.rb +++ b/spec/requests/projects/settings/packages_and_registries_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Settings::PackagesAndRegistriesController do +RSpec.describe Projects::Settings::PackagesAndRegistriesController, feature_category: :package_registry do let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) } diff --git a/spec/requests/projects/tags_controller_spec.rb b/spec/requests/projects/tags_controller_spec.rb index b9531a2739c..c0b0b1728c2 100644 --- a/spec/requests/projects/tags_controller_spec.rb +++ b/spec/requests/projects/tags_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::TagsController do +RSpec.describe Projects::TagsController, feature_category: :source_code_management do context 'token authentication' do context 'when public project' do let_it_be(:public_project) { create(:project, :repository, :public) } diff --git a/spec/requests/projects/uploads_spec.rb b/spec/requests/projects/uploads_spec.rb index de5ef36be7e..aec2636b69c 100644 --- a/spec/requests/projects/uploads_spec.rb +++ b/spec/requests/projects/uploads_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'File uploads' do +RSpec.describe 'File uploads', feature_category: :not_owned do include WorkhorseHelpers let(:project) { create(:project, :public, :repository) } diff --git a/spec/requests/projects/usage_quotas_spec.rb b/spec/requests/projects/usage_quotas_spec.rb index 6e449a21804..60ab64c30c3 100644 --- a/spec/requests/projects/usage_quotas_spec.rb +++ b/spec/requests/projects/usage_quotas_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project Usage Quotas' do +RSpec.describe 'Project Usage Quotas', feature_category: :subscription_cost_management do let_it_be(:project) { create(:project) } let_it_be(:role) { :maintainer } let_it_be(:user) { create(:user) } diff --git a/spec/requests/projects/work_items_spec.rb b/spec/requests/projects/work_items_spec.rb index 4d7acc73d4f..056416d380d 100644 --- a/spec/requests/projects/work_items_spec.rb +++ b/spec/requests/projects/work_items_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Work Items' do +RSpec.describe 'Work Items', feature_category: :team_planning do let_it_be(:work_item) { create(:work_item) } let_it_be(:developer) { create(:user) } diff --git a/spec/requests/projects_controller_spec.rb b/spec/requests/projects_controller_spec.rb index d2200d5a4ec..f08f3578dc0 100644 --- a/spec/requests/projects_controller_spec.rb +++ b/spec/requests/projects_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ProjectsController do +RSpec.describe ProjectsController, feature_category: :projects do context 'token authentication' do context 'when public project' do let_it_be(:public_project) { create(:project, :public) } diff --git a/spec/requests/pwa_controller_spec.rb b/spec/requests/pwa_controller_spec.rb index 7a295b17231..3971790c094 100644 --- a/spec/requests/pwa_controller_spec.rb +++ b/spec/requests/pwa_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PwaController do +RSpec.describe PwaController, feature_category: :navigation do describe 'GET #manifest' do it 'responds with json' do get manifest_path(format: :json) @@ -10,6 +10,23 @@ RSpec.describe PwaController do expect(response.body).to include('The complete DevOps platform.') expect(response).to have_gitlab_http_status(:success) end + + context 'with customized appearance' do + let_it_be(:appearance) do + create(:appearance, title: 'Long name', short_title: 'Short name', description: 'This is a test') + end + + it 'uses custom values', :aggregate_failures do + get manifest_path(format: :json) + + expect(Gitlab::Json.parse(response.body)).to include({ + 'description' => 'This is a test', + 'name' => 'Long name', + 'short_name' => 'Short name' + }) + expect(response).to have_gitlab_http_status(:success) + end + end end describe 'GET #offline' do diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index f6b9bc527ac..643a98da441 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_caching do +RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_caching, +feature_category: :authentication_and_authorization do include RackAttackSpecHelpers include SessionHelpers diff --git a/spec/requests/recursive_webhook_detection_spec.rb b/spec/requests/recursive_webhook_detection_spec.rb index fe27c90b6c8..a74d4f9a603 100644 --- a/spec/requests/recursive_webhook_detection_spec.rb +++ b/spec/requests/recursive_webhook_detection_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_redis_shared_state, :request_store do +RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_redis_shared_state, :request_store, +feature_category: :integrations do include StubRequests let_it_be(:user) { create(:user) } diff --git a/spec/requests/robots_txt_spec.rb b/spec/requests/robots_txt_spec.rb index 7c0b7d8117a..18a14677e0c 100644 --- a/spec/requests/robots_txt_spec.rb +++ b/spec/requests/robots_txt_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Robots.txt Requests', :aggregate_failures do +RSpec.describe 'Robots.txt Requests', :aggregate_failures, feature_category: :build do before do Gitlab::Testing::RobotsBlockerMiddleware.block_requests! end diff --git a/spec/requests/runner_setup_controller_spec.rb b/spec/requests/runner_setup_controller_spec.rb index 665c896e30d..8d75b9e81b7 100644 --- a/spec/requests/runner_setup_controller_spec.rb +++ b/spec/requests/runner_setup_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe RunnerSetupController do +RSpec.describe RunnerSetupController, feature_category: :runner_fleet do let(:user) { create(:user) } before do diff --git a/spec/requests/sandbox_controller_spec.rb b/spec/requests/sandbox_controller_spec.rb index 4fc26580123..77913065380 100644 --- a/spec/requests/sandbox_controller_spec.rb +++ b/spec/requests/sandbox_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SandboxController do +RSpec.describe SandboxController, feature_category: :not_owned do describe 'GET #mermaid' do it 'renders page without template' do get sandbox_mermaid_path diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index 613732c19ea..98dda75a2b0 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -2,10 +2,11 @@ require 'spec_helper' -RSpec.describe SearchController, type: :request do +RSpec.describe SearchController, type: :request, feature_category: :global_search do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :public, :repository, :wiki_repo, name: 'awesome project', group: group) } + let_it_be(:projects) { create_list(:project, 5, :public, :repository, :wiki_repo) } before do login_as(user) @@ -20,9 +21,16 @@ RSpec.describe SearchController, type: :request do create(object, *creation_traits, creation_args) control = ActiveRecord::QueryRecorder.new(skip_cached: false) { send_search_request(params) } - create_list(object, 3, *creation_traits, creation_args) + expect(response.body).to include('search-results') # Confirm there are search results to prevent false positives + + projects.each do |project| + creation_args[:source_project] = project if creation_args.key?(:source_project) + creation_args[:project] = project if creation_args.key?(:project) + create(object, *creation_traits, creation_args) + end expect { send_search_request(params) }.not_to exceed_all_query_limit(control).with_threshold(threshold) + expect(response.body).to include('search-results') # Confirm there are search results to prevent false positives end end @@ -33,26 +41,26 @@ RSpec.describe SearchController, type: :request do let(:object) { :issue } let(:creation_args) { { project: project, title: 'foo' } } let(:params) { { search: 'foo', scope: 'issues' } } - # there are 4 additional queries run for the logged in user: - # (1) geo_nodes, (1) users, (2) broadcast_messages - let(:threshold) { 4 } + # some N+1 queries still exist + # each issue runs an extra query for group namespaces + let(:threshold) { 1 } it_behaves_like 'an efficient database result' end - context 'for merge_request scope' do + context 'for merge_requests scope' do let(:creation_traits) { [:unique_branches] } let(:object) { :merge_request } let(:creation_args) { { source_project: project, title: 'bar' } } let(:params) { { search: 'bar', scope: 'merge_requests' } } - # there are 4 additional queries run for the logged in user: - # - (1) geo_nodes, (1) users, (2) broadcast_messages + # some N+1 queries still exist + # each merge request runs an extra query for project routes let(:threshold) { 4 } it_behaves_like 'an efficient database result' end - context 'for project scope' do + context 'for projects scope' do let(:creation_traits) { [:public] } let(:object) { :project } let(:creation_args) { { name: 'project' } } @@ -63,12 +71,76 @@ RSpec.describe SearchController, type: :request do # - one count for open MRs # - one count for open Issues # there are 4 additional queries run for the logged in user: - # (1) geo_nodes, (1) users, (2) broadcast_messages - let(:threshold) { 13 } + # (1) user preferences, (1) user statuses, (1) user details, (1) users + let(:threshold) { 17 } it_behaves_like 'an efficient database result' end + context 'for milestones scope' do + let(:object) { :milestone } + let(:creation_args) { { project: project } } + let(:params) { { search: 'title', scope: 'milestones' } } + let(:threshold) { 0 } + + it_behaves_like 'an efficient database result' + end + + context 'for users scope' do + let(:object) { :user } + let(:creation_args) { { name: 'georgia' } } + let(:params) { { search: 'georgia', scope: 'users' } } + let(:threshold) { 0 } + + it_behaves_like 'an efficient database result' + end + + context 'for notes scope' do + let(:creation_traits) { [:on_commit] } + let(:object) { :note } + let(:creation_args) { { project: project, note: 'hello world' } } + let(:params) { { search: 'hello world', scope: 'notes', project_id: project.id } } + let(:threshold) { 0 } + + it_behaves_like 'an efficient database result' + end + + context 'for blobs scope' do + # blobs are enabled for project search only in basic search + let(:params_for_one) { { search: 'test', project_id: project.id, scope: 'blobs', per_page: 1 } } + let(:params_for_many) { { search: 'test', project_id: project.id, scope: 'blobs', per_page: 5 } } + + it 'avoids N+1 database queries' do + control = ActiveRecord::QueryRecorder.new { send_search_request(params_for_one) } + expect(response.body).to include('search-results') # Confirm search results to prevent false positives + + expect { send_search_request(params_for_many) }.not_to exceed_query_limit(control.count) + expect(response.body).to include('search-results') # Confirm search results to prevent false positives + end + end + + context 'for commits scope' do + let(:params_for_one) { { search: 'test', project_id: project.id, scope: 'commits', per_page: 1 } } + let(:params_for_many) { { search: 'test', project_id: project.id, scope: 'commits', per_page: 5 } } + + it 'avoids N+1 database queries' do + control = ActiveRecord::QueryRecorder.new { send_search_request(params_for_one) } + expect(response.body).to include('search-results') # Confirm search results to prevent false positives + + expect { send_search_request(params_for_many) }.not_to exceed_query_limit(control.count) + expect(response.body).to include('search-results') # Confirm search results to prevent false positives + end + end + + context 'for code search' do + let(:params_for_code_search) { { search: 'blob: hello' } } + + it 'sets scope to blobs if code search literals are used' do + send_search_request(params_for_code_search) + expect(response).to redirect_to(search_path(params_for_code_search.merge({ scope: 'blobs' }))) + end + end + context 'when searching by SHA' do let(:sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } diff --git a/spec/requests/self_monitoring_project_spec.rb b/spec/requests/self_monitoring_project_spec.rb index 64c5f94657d..ce4dd10a52d 100644 --- a/spec/requests/self_monitoring_project_spec.rb +++ b/spec/requests/self_monitoring_project_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Self-Monitoring project requests' do +RSpec.describe 'Self-Monitoring project requests', feature_category: :projects 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 95df181b7b0..7b3fd23980a 100644 --- a/spec/requests/sessions_spec.rb +++ b/spec/requests/sessions_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Sessions' do +RSpec.describe 'Sessions', feature_category: :authentication_and_authorization do context 'authentication', :allow_forgery_protection do let(:user) { create(:user) } diff --git a/spec/requests/terraform/services_controller_spec.rb b/spec/requests/terraform/services_controller_spec.rb index 54f7348513e..928c57613fa 100644 --- a/spec/requests/terraform/services_controller_spec.rb +++ b/spec/requests/terraform/services_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Terraform::ServicesController do +RSpec.describe Terraform::ServicesController, feature_category: :package_registry do describe 'GET /.well-known/terraform.json' do subject { get '/.well-known/terraform.json' } diff --git a/spec/requests/user_activity_spec.rb b/spec/requests/user_activity_spec.rb index 148bb2d6fae..f9682d81640 100644 --- a/spec/requests/user_activity_spec.rb +++ b/spec/requests/user_activity_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Update of user activity' do +RSpec.describe 'Update of user activity', feature_category: :users do paths_to_visit = [ '/group', '/group/project', diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb index 1397741af18..4e3c2744d56 100644 --- a/spec/requests/user_avatar_spec.rb +++ b/spec/requests/user_avatar_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Loading a user avatar' do +RSpec.describe 'Loading a user avatar', feature_category: :users do let(:user) { create(:user, :with_avatar) } context 'when logged in' do diff --git a/spec/requests/user_sends_malformed_strings_spec.rb b/spec/requests/user_sends_malformed_strings_spec.rb index da533606be5..4c131bfe452 100644 --- a/spec/requests/user_sends_malformed_strings_spec.rb +++ b/spec/requests/user_sends_malformed_strings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User sends malformed strings' do +RSpec.describe 'User sends malformed strings', feature_category: :user_management do include GitHttpHelpers let(:null_byte) { "\u0000" } diff --git a/spec/requests/user_spoofs_ip_spec.rb b/spec/requests/user_spoofs_ip_spec.rb index 833dae78529..0244d60bc3b 100644 --- a/spec/requests/user_spoofs_ip_spec.rb +++ b/spec/requests/user_spoofs_ip_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User spoofs their IP' do +RSpec.describe 'User spoofs their IP', feature_category: :user_management do it 'raises a 400 error' do get '/nonexistent', headers: { 'Client-Ip' => '1.2.3.4', 'X-Forwarded-For' => '5.6.7.8' } diff --git a/spec/requests/users/group_callouts_spec.rb b/spec/requests/users/group_callouts_spec.rb index a8680c3add4..d186bf92bc7 100644 --- a/spec/requests/users/group_callouts_spec.rb +++ b/spec/requests/users/group_callouts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Group callouts' do +RSpec.describe 'Group callouts', feature_category: :navigation do let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } diff --git a/spec/requests/users/project_callouts_spec.rb b/spec/requests/users/project_callouts_spec.rb index 98c00fef052..a15dd225e84 100644 --- a/spec/requests/users/project_callouts_spec.rb +++ b/spec/requests/users/project_callouts_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project callouts' do +RSpec.describe 'Project callouts', feature_category: :navigation do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index e78d4cc326e..608284c05f3 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe UsersController do +RSpec.describe UsersController, feature_category: :user_management do # This user should have the same e-mail address associated with the GPG key prepared for tests let(:user) { create(:user, email: GpgHelpers::User1.emails[0]) } let(:private_user) { create(:user, private_profile: true) } diff --git a/spec/requests/verifies_with_email_spec.rb b/spec/requests/verifies_with_email_spec.rb index 34fda1cce4d..cac754a9cb1 100644 --- a/spec/requests/verifies_with_email_spec.rb +++ b/spec/requests/verifies_with_email_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe 'VerifiesWithEmail', :clean_gitlab_redis_sessions, :clean_gitlab_redis_rate_limiting do +RSpec.describe 'VerifiesWithEmail', :clean_gitlab_redis_sessions, :clean_gitlab_redis_rate_limiting, +feature_category: :user_management do include SessionHelpers include EmailHelpers @@ -78,15 +79,25 @@ RSpec.describe 'VerifiesWithEmail', :clean_gitlab_redis_sessions, :clean_gitlab_ end context 'when the user is signing in from an unknown ip address' do + let(:ip_check_enabled) { true } + before do + stub_feature_flags(check_ip_address_for_email_verification: ip_check_enabled) allow(AuthenticationEvent) .to receive(:initial_login_or_known_ip_address?) .and_return(false) + perform_enqueued_jobs { sign_in } end it_behaves_like 'send verification instructions' it_behaves_like 'prompt for email verification' + + context 'when the check_ip_address_for_email_verification feature flag is disabled' do + let(:ip_check_enabled) { false } + + it_behaves_like 'not verifying with email' + end end end @@ -187,6 +198,18 @@ RSpec.describe 'VerifiesWithEmail', :clean_gitlab_redis_sessions, :clean_gitlab_ expect(response).to redirect_to(users_successful_verification_path) end end + + context 'when not completing identity verification and logging in with another account' do + let(:another_user) { create(:user) } + + before do + post user_session_path, params: { user: { login: another_user.username, password: another_user.password } } + end + + it 'does not redirect to the successful verification path' do + expect(response).not_to redirect_to(users_successful_verification_path) + end + end end context 'when signing in with a valid password' do diff --git a/spec/requests/web_ide/remote_ide_controller_spec.rb b/spec/requests/web_ide/remote_ide_controller_spec.rb new file mode 100644 index 00000000000..367c7527f10 --- /dev/null +++ b/spec/requests/web_ide/remote_ide_controller_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WebIde::RemoteIdeController, feature_category: :remote_development do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + + let_it_be(:top_nav_partial) { 'layouts/header/_default' } + + let_it_be(:connection_token) { 'random1Connection3Token7' } + let_it_be(:remote_path) { 'test/foo/README.md' } + let_it_be(:return_url) { 'https://example.com/-/original/location' } + let_it_be(:csp_nonce) { 'just=some=noncense' } + + let(:remote_host) { 'my-remote-host.example.com:1234' } + let(:ff_vscode_web_ide) { true } + + before do + sign_in(user) + + stub_feature_flags(vscode_web_ide: ff_vscode_web_ide) + + allow_next_instance_of(described_class) do |instance| + allow(instance).to receive(:content_security_policy_nonce).and_return(csp_nonce) + end + end + + shared_examples_for '404 response' do + it 'has not_found status' do + post_to_remote_ide + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + describe "#index" do + context "when feature flag is on *and* user is not using legacy Web IDE" do + before do + post_to_remote_ide + end + + it "renders the correct layout" do + expect(response).to render_template(layout: 'fullscreen') + end + + it "renders with minimal: true" do + # This indirectly tests that `minimal: true` was passed to the fullscreen layout + expect(response).not_to render_template(top_nav_partial) + end + + it "renders root element with data" do + expected = { + connection_token: connection_token, + remote_host: remote_host, + remote_path: remote_path, + return_url: return_url, + csp_nonce: csp_nonce + } + + expect(find_root_element_data).to eq(expected) + end + + it "updates the content security policy with the correct connect sources" do + expect(find_csp_source('connect-src')).to include( + "ws://#{remote_host}", + "wss://#{remote_host}", + "http://#{remote_host}", + "https://#{remote_host}" + ) + end + + it "updates the content security policy with the correct frame sources" do + expect(find_csp_source('frame-src')).to include("https://*.vscode-cdn.net/") + end + end + + context 'when remote_host does not have port' do + let(:remote_host) { "my-remote-host.example.com" } + + before do + post_to_remote_ide + end + + it "updates the content security policy with the correct remote_host" do + expect(find_csp_source('connect-src')).to include( + "ws://#{remote_host}", + "wss://#{remote_host}", + "http://#{remote_host}", + "https://#{remote_host}" + ) + end + + it 'renders remote_host in root element data' do + expect(find_root_element_data).to include(remote_host: remote_host) + end + end + + context 'when feature flag is off' do + let(:ff_vscode_web_ide) { false } + + it_behaves_like '404 response' + end + + context "when the remote host is invalid" do + let(:remote_host) { 'invalid:host:1:1:' } + + it_behaves_like '404 response' + end + end + + def find_root_element_data + ide_attrs = Nokogiri::HTML.parse(response.body).at_css('#ide').attributes.transform_values(&:value) + + { + connection_token: ide_attrs['data-connection-token'], + remote_host: ide_attrs['data-remote-host'], + remote_path: ide_attrs['data-remote-path'], + return_url: ide_attrs['data-return-url'], + csp_nonce: ide_attrs['data-csp-nonce'] + } + end + + def find_csp_source(key) + csp = response.headers['Content-Security-Policy'] + + # Transform "default-src foo bar; connect-src foo bar; script-src ..." + # into array of values for a single directive based on the given key + csp.split(';') + .map(&:strip) + .find { |entry| entry.starts_with?(key) } + .split(' ') + .drop(1) + end + + def post_to_remote_ide + params = { + connection_token: connection_token, + return_url: return_url + } + + post ide_remote_path(remote_host: remote_host, remote_path: remote_path), params: params + end +end diff --git a/spec/requests/whats_new_controller_spec.rb b/spec/requests/whats_new_controller_spec.rb index d4976a2bba3..1e8cb514945 100644 --- a/spec/requests/whats_new_controller_spec.rb +++ b/spec/requests/whats_new_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe WhatsNewController, :clean_gitlab_redis_cache do +RSpec.describe WhatsNewController, :clean_gitlab_redis_cache, feature_category: :navigation do after do ReleaseHighlight.instance_variable_set(:@file_paths, nil) end |