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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-20 13:43:29 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-20 13:43:29 +0300
commit3b1af5cc7ed2666ff18b718ce5d30fa5a2756674 (patch)
tree3bc4a40e0ee51ec27eabf917c537033c0c5b14d4 /spec/requests/api
parent9bba14be3f2c211bf79e15769cd9b77bc73a13bc (diff)
Add latest changes from gitlab-org/gitlab@16-1-stable-eev16.1.0-rc42
Diffstat (limited to 'spec/requests/api')
-rw-r--r--spec/requests/api/admin/batched_background_migrations_spec.rb21
-rw-r--r--spec/requests/api/admin/dictionary_spec.rb59
-rw-r--r--spec/requests/api/admin/migrations_spec.rb89
-rw-r--r--spec/requests/api/admin/plan_limits_spec.rb8
-rw-r--r--spec/requests/api/api_spec.rb4
-rw-r--r--spec/requests/api/badges_spec.rb2
-rw-r--r--spec/requests/api/ci/runner/jobs_artifacts_spec.rb24
-rw-r--r--spec/requests/api/ci/runner/jobs_put_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/jobs_trace_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/runners_delete_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/runners_post_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/runners_reset_spec.rb4
-rw-r--r--spec/requests/api/ci/runner/runners_verify_post_spec.rb4
-rw-r--r--spec/requests/api/ci/secure_files_spec.rb20
-rw-r--r--spec/requests/api/clusters/agent_tokens_spec.rb22
-rw-r--r--spec/requests/api/debian_project_packages_spec.rb1
-rw-r--r--spec/requests/api/deploy_keys_spec.rb28
-rw-r--r--spec/requests/api/deployments_spec.rb18
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb4
-rw-r--r--spec/requests/api/error_tracking/collector_spec.rb233
-rw-r--r--spec/requests/api/features_spec.rb83
-rw-r--r--spec/requests/api/generic_packages_spec.rb4
-rw-r--r--spec/requests/api/graphql/audit_events/definitions_spec.rb24
-rw-r--r--spec/requests/api/graphql/ci/group_environment_scopes_spec.rb68
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb6
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb2
-rw-r--r--spec/requests/api/graphql/ci/stages_spec.rb6
-rw-r--r--spec/requests/api/graphql/current_user/groups_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb46
-rw-r--r--spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb21
-rw-r--r--spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb21
-rw-r--r--spec/requests/api/graphql/group/group_members_spec.rb2
-rw-r--r--spec/requests/api/graphql/group_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/groups_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/jobs_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/metadata_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb85
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/http_integration/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/http_integration/destroy_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/http_integration/reset_token_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb17
-rw-r--r--spec/requests/api/graphql/mutations/ci/project_ci_cd_settings_update_spec.rb29
-rw-r--r--spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb21
-rw-r--r--spec/requests/api/graphql/mutations/dependency_proxy/image_ttl_group_policy/update_spec.rb27
-rw-r--r--spec/requests/api/graphql/mutations/environments/create_spec.rb60
-rw-r--r--spec/requests/api/graphql/mutations/environments/delete_spec.rb33
-rw-r--r--spec/requests/api/graphql/mutations/environments/update_spec.rb70
-rw-r--r--spec/requests/api/graphql/mutations/groups/update_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/jira_import/import_users_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/jira_import/start_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/members/projects/bulk_update_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb17
-rw-r--r--spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb18
-rw-r--r--spec/requests/api/graphql/mutations/snippets/create_spec.rb8
-rw-r--r--spec/requests/api/graphql/mutations/snippets/update_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/work_items/update_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb8
-rw-r--r--spec/requests/api/graphql/namespace_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/alert_management/integrations_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/environments_spec.rb48
-rw-r--r--spec/requests/api/graphql/project/jira_import_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/pipeline_spec.rb17
-rw-r--r--spec/requests/api/graphql/project/project_members_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/work_items_spec.rb76
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/subscriptions/work_item_updated_spec.rb43
-rw-r--r--spec/requests/api/graphql/user/group_member_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user/project_member_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/user/starred_projects_query_spec.rb2
-rw-r--r--spec/requests/api/graphql/users/set_namespace_commit_email_spec.rb106
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb111
-rw-r--r--spec/requests/api/group_avatar_spec.rb2
-rw-r--r--spec/requests/api/groups_spec.rb2
-rw-r--r--spec/requests/api/integrations_spec.rb19
-rw-r--r--spec/requests/api/internal/base_spec.rb117
-rw-r--r--spec/requests/api/internal/error_tracking_spec.rb4
-rw-r--r--spec/requests/api/internal/kubernetes_spec.rb80
-rw-r--r--spec/requests/api/issues/post_projects_issues_spec.rb7
-rw-r--r--spec/requests/api/markdown_spec.rb14
-rw-r--r--spec/requests/api/maven_packages_spec.rb21
-rw-r--r--spec/requests/api/members_spec.rb2
-rw-r--r--spec/requests/api/ml/mlflow/experiments_spec.rb5
-rw-r--r--spec/requests/api/ml/mlflow/runs_spec.rb5
-rw-r--r--spec/requests/api/ml_model_packages_spec.rb200
-rw-r--r--spec/requests/api/namespaces_spec.rb6
-rw-r--r--spec/requests/api/npm_group_packages_spec.rb186
-rw-r--r--spec/requests/api/npm_project_packages_spec.rb2
-rw-r--r--spec/requests/api/nuget_group_packages_spec.rb8
-rw-r--r--spec/requests/api/pages_domains_spec.rb4
-rw-r--r--spec/requests/api/project_attributes.yml4
-rw-r--r--spec/requests/api/project_export_spec.rb2
-rw-r--r--spec/requests/api/project_hooks_spec.rb2
-rw-r--r--spec/requests/api/project_job_token_scope_spec.rb440
-rw-r--r--spec/requests/api/project_packages_spec.rb243
-rw-r--r--spec/requests/api/project_templates_spec.rb45
-rw-r--r--spec/requests/api/projects_spec.rb12
-rw-r--r--spec/requests/api/release/links_spec.rb14
-rw-r--r--spec/requests/api/releases_spec.rb32
-rw-r--r--spec/requests/api/resource_access_tokens_spec.rb25
-rw-r--r--spec/requests/api/search_spec.rb16
-rw-r--r--spec/requests/api/settings_spec.rb23
-rw-r--r--spec/requests/api/system_hooks_spec.rb2
-rw-r--r--spec/requests/api/topics_spec.rb2
-rw-r--r--spec/requests/api/usage_data_non_sql_metrics_spec.rb2
-rw-r--r--spec/requests/api/usage_data_queries_spec.rb2
-rw-r--r--spec/requests/api/users_spec.rb6
-rw-r--r--spec/requests/api/v3/github_spec.rb23
112 files changed, 2534 insertions, 737 deletions
diff --git a/spec/requests/api/admin/batched_background_migrations_spec.rb b/spec/requests/api/admin/batched_background_migrations_spec.rb
index e88fba3fbe7..180b6c7abd6 100644
--- a/spec/requests/api/admin/batched_background_migrations_spec.rb
+++ b/spec/requests/api/admin/batched_background_migrations_spec.rb
@@ -50,6 +50,27 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab
show_migration
end
+
+ context 'when migration has completed jobs' do
+ let(:migration) do
+ Gitlab::Database::SharedModel.using_connection(ci_model.connection) do
+ create(:batched_background_migration, :active, total_tuple_count: 100)
+ end
+ end
+
+ let!(:batched_job) do
+ Gitlab::Database::SharedModel.using_connection(ci_model.connection) do
+ create(:batched_background_migration_job, :succeeded, batched_migration: migration, batch_size: 8)
+ end
+ end
+
+ it 'calculates the progress using the CI database' do
+ show_migration
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['progress']).to eq(8)
+ end
+ end
end
context 'when the database name does not exist' do
diff --git a/spec/requests/api/admin/dictionary_spec.rb b/spec/requests/api/admin/dictionary_spec.rb
new file mode 100644
index 00000000000..effd3572423
--- /dev/null
+++ b/spec/requests/api/admin/dictionary_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Admin::Dictionary, feature_category: :database do
+ let(:admin) { create(:admin) }
+ let(:path) { "/admin/databases/main/dictionary/tables/achievements" }
+
+ describe 'GET admin/databases/:database_name/dictionary/tables/:table_name' do
+ it_behaves_like "GET request permissions for admin mode"
+
+ subject(:show_table_dictionary) do
+ get api(path, admin, admin_mode: true)
+ end
+
+ context 'when the database does not exist' do
+ it 'returns bad request' do
+ get api("/admin/databases/#{non_existing_record_id}/dictionary/tables/achievements", admin, admin_mode: true)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when the table does not exist' do
+ it 'returns not found' do
+ get api("/admin/databases/main/dictionary/tables/#{non_existing_record_id}", admin, admin_mode: true)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'with a malicious table_name' do
+ it 'returns an error' do
+ get api("/admin/databases/main/dictionary/tables/%2E%2E%2Fpasswords.yml", admin, admin_mode: true)
+
+ expect(response).to have_gitlab_http_status(:error)
+ end
+ end
+
+ context 'when the params are correct' do
+ let(:dictionary_dir) { Rails.root.join('spec/fixtures') }
+ let(:path_file) { Rails.root.join(dictionary_dir, 'achievements.yml') }
+
+ it 'fetches the table dictionary' do
+ allow(Gitlab::Database::GitlabSchema).to receive(:dictionary_paths).and_return([dictionary_dir])
+
+ expect(Gitlab::PathTraversal).to receive(:check_allowed_absolute_path_and_path_traversal!).twice.with(
+ path_file.to_s, [dictionary_dir.to_s]).and_call_original
+
+ show_table_dictionary
+
+ aggregate_failures "testing response" do
+ expect(json_response['table_name']).to eq('achievements')
+ expect(json_response['feature_categories']).to eq(['feature_category_example'])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/admin/migrations_spec.rb b/spec/requests/api/admin/migrations_spec.rb
new file mode 100644
index 00000000000..fc464300b56
--- /dev/null
+++ b/spec/requests/api/admin/migrations_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Admin::Migrations, feature_category: :database do
+ let(:admin) { create(:admin) }
+
+ describe 'POST /admin/migrations/:version/mark' do
+ let(:database) { :main }
+ let(:params) { { database: database } }
+ let(:connection) { ApplicationRecord.connection }
+ let(:path) { "/admin/migrations/#{version}/mark" }
+ let(:version) { 1 }
+
+ subject(:mark) do
+ post api(path, admin, admin_mode: true), params: params
+ end
+
+ context 'when the migration exists' do
+ before do
+ double = instance_double(
+ Database::MarkMigrationService,
+ execute: ServiceResponse.success)
+
+ allow(Database::MarkMigrationService)
+ .to receive(:new)
+ .with(connection: connection, version: version)
+ .and_return(double)
+ end
+
+ it_behaves_like "POST request permissions for admin mode"
+
+ it 'marks the migration as successful' do
+ mark
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when the migration does not exist' do
+ let(:version) { 123 }
+
+ it 'returns 404' do
+ mark
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the migration was already executed' do
+ let(:version) { connection.migration_context.current_version }
+
+ it 'returns 422' do
+ mark
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'when multiple database is enabled' do
+ let(:ci_model) { Ci::ApplicationRecord }
+ let(:database) { :ci }
+
+ before do
+ skip_if_multiple_databases_not_setup(:ci)
+ end
+
+ it 'uses the correct connection' do
+ expect(Database::MarkMigrationService)
+ .to receive(:new)
+ .with(connection: ci_model.connection, version: version)
+ .and_call_original
+
+ mark
+ end
+
+ context 'when the database name does not exist' do
+ let(:database) { :wrong_database }
+
+ it 'returns bad request', :aggregate_failures do
+ mark
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.body).to include('database does not have a valid value')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/admin/plan_limits_spec.rb b/spec/requests/api/admin/plan_limits_spec.rb
index 6085b48c7c2..cad1111b76b 100644
--- a/spec/requests/api/admin/plan_limits_spec.rb
+++ b/spec/requests/api/admin/plan_limits_spec.rb
@@ -99,9 +99,11 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :shared d
'ci_registered_group_runners': 107,
'ci_registered_project_runners': 108,
'conan_max_file_size': 10,
+ 'enforcement_limit': 15,
'generic_packages_max_file_size': 20,
'helm_max_file_size': 25,
'maven_max_file_size': 30,
+ 'notification_limit': 90,
'npm_max_file_size': 40,
'nuget_max_file_size': 50,
'pypi_max_file_size': 60,
@@ -119,9 +121,11 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :shared d
expect(json_response['ci_registered_group_runners']).to eq(107)
expect(json_response['ci_registered_project_runners']).to eq(108)
expect(json_response['conan_max_file_size']).to eq(10)
+ expect(json_response['enforcement_limit']).to eq(15)
expect(json_response['generic_packages_max_file_size']).to eq(20)
expect(json_response['helm_max_file_size']).to eq(25)
expect(json_response['maven_max_file_size']).to eq(30)
+ expect(json_response['notification_limit']).to eq(90)
expect(json_response['npm_max_file_size']).to eq(40)
expect(json_response['nuget_max_file_size']).to eq(50)
expect(json_response['pypi_max_file_size']).to eq(60)
@@ -163,9 +167,11 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :shared d
'ci_registered_group_runners': 't',
'ci_registered_project_runners': 's',
'conan_max_file_size': 'a',
+ 'enforcement_limit': 'e',
'generic_packages_max_file_size': 'b',
'helm_max_file_size': 'h',
'maven_max_file_size': 'c',
+ 'notification_limit': 'n',
'npm_max_file_size': 'd',
'nuget_max_file_size': 'e',
'pypi_max_file_size': 'f',
@@ -184,9 +190,11 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits', feature_category: :shared d
'ci_registered_group_runners is invalid',
'ci_registered_project_runners is invalid',
'conan_max_file_size is invalid',
+ 'enforcement_limit is invalid',
'generic_packages_max_file_size is invalid',
'helm_max_file_size is invalid',
'maven_max_file_size is invalid',
+ 'notification_limit is invalid',
'npm_max_file_size is invalid',
'nuget_max_file_size is invalid',
'pypi_max_file_size is invalid',
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index 219c7dbdbc5..01bb8101f76 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -371,10 +371,10 @@ RSpec.describe API::API, feature_category: :system_access do
)
end
- it 'returns 429 status with exhausted' do
+ it 'returns 503 status and Retry-After header' do
get api("/projects/#{project.id}/repository/commits", user)
- expect(response).to have_gitlab_http_status(:too_many_requests)
+ expect(response).to have_gitlab_http_status(:service_unavailable)
expect(response.headers['Retry-After']).to be(50)
expect(json_response).to eql(
'message' => 'Upstream Gitaly has been exhausted. Try again later'
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
index 1c09c1129a2..0b340b95b20 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, feature_category: :projects do
+RSpec.describe API::Badges, feature_category: :groups_and_projects do
let(:maintainer) { create(:user, username: 'maintainer_user') }
let(:developer) { create(:user) }
let(:access_requester) { create(:user) }
diff --git a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
index 596af1110cc..2e0be23ba90 100644
--- a/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_artifacts_spec.rb
@@ -137,6 +137,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
let(:send_request) { subject }
end
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { subject }
+ end
+
it "doesn't update runner info" do
expect { subject }.not_to change { runner.reload.contacted_at }
end
@@ -177,18 +181,6 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
expect(json_response['RemoteObject']['SkipDelete']).to eq(true)
expect(json_response['MaximumSize']).not_to be_nil
end
-
- context 'when ci_artifacts_upload_to_final_location flag is disabled' do
- before do
- stub_feature_flags(ci_artifacts_upload_to_final_location: false)
- end
-
- it 'does not skip delete' do
- subject
-
- expect(json_response['RemoteObject']['SkipDelete']).to eq(false)
- end
- end
end
context 'when direct upload is disabled' do
@@ -299,6 +291,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
end
end
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { upload_artifacts(file_upload, headers_with_token) }
+ end
+
it "doesn't update runner info" do
expect { upload_artifacts(file_upload, headers_with_token) }.not_to change { runner.reload.contacted_at }
end
@@ -901,6 +897,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
let(:send_request) { download_artifact }
end
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { download_artifact }
+ end
+
it "doesn't update runner info" do
expect { download_artifact }.not_to change { runner.reload.contacted_at }
end
diff --git a/spec/requests/api/ci/runner/jobs_put_spec.rb b/spec/requests/api/ci/runner/jobs_put_spec.rb
index ab7ab4e74f8..65489ea7015 100644
--- a/spec/requests/api/ci/runner/jobs_put_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_put_spec.rb
@@ -38,6 +38,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
let(:send_request) { update_job(state: 'success') }
end
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { update_job(state: 'success') }
+ end
+
it 'updates runner info' do
expect { update_job(state: 'success') }.to change { runner.reload.contacted_at }
.and change { runner_manager.reload.contacted_at }
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 0164eda7680..ca57208eb1d 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -90,6 +90,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
end
end
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { post api('/jobs/request') }
+ end
+
context 'when no token is provided' do
it 'returns 400 error' do
post api('/jobs/request')
diff --git a/spec/requests/api/ci/runner/jobs_trace_spec.rb b/spec/requests/api/ci/runner/jobs_trace_spec.rb
index de67cec0a27..ee00fc5a793 100644
--- a/spec/requests/api/ci/runner/jobs_trace_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_trace_spec.rb
@@ -45,6 +45,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks, feature_catego
let(:send_request) { patch_the_trace }
end
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { patch_the_trace }
+ end
+
it 'updates runner info' do
runner.update!(contacted_at: 1.year.ago)
diff --git a/spec/requests/api/ci/runner/runners_delete_spec.rb b/spec/requests/api/ci/runner/runners_delete_spec.rb
index 681dd4d701e..d1488828bad 100644
--- a/spec/requests/api/ci/runner/runners_delete_spec.rb
+++ b/spec/requests/api/ci/runner/runners_delete_spec.rb
@@ -21,6 +21,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
end
describe 'DELETE /api/v4/runners' do
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { delete api('/runners') }
+ end
+
context 'when no token is provided' do
it 'returns 400 error' do
delete api('/runners')
diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb
index a36ea2115cf..c5e49e9ac54 100644
--- a/spec/requests/api/ci/runner/runners_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_post_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
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
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { post api('/runners') }
+ end
+
context 'when no token is provided' do
it 'returns 400 error' do
post api('/runners')
diff --git a/spec/requests/api/ci/runner/runners_reset_spec.rb b/spec/requests/api/ci/runner/runners_reset_spec.rb
index 2d1e366e820..03cb6238fc1 100644
--- a/spec/requests/api/ci/runner/runners_reset_spec.rb
+++ b/spec/requests/api/ci/runner/runners_reset_spec.rb
@@ -19,6 +19,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token_expires_at: 1.day.from_now) }
describe 'POST /runners/reset_authentication_token', :freeze_time do
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { post api("/runners/reset_authentication_token") }
+ end
+
context 'current token provided' do
it "resets authentication token when token doesn't have an expiration" do
expect do
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 f1b33826f5e..e6af61ca7e0 100644
--- a/spec/requests/api/ci/runner/runners_verify_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_verify_post_spec.rb
@@ -24,6 +24,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego
subject(:verify) { post api('/runners/verify'), params: params }
+ it_behaves_like 'runner migrations backoff' do
+ let(:request) { verify }
+ end
+
context 'when no token is provided' do
it 'returns 400 error' do
post api('/runners/verify')
diff --git a/spec/requests/api/ci/secure_files_spec.rb b/spec/requests/api/ci/secure_files_spec.rb
index db12576154e..4e1afd66683 100644
--- a/spec/requests/api/ci/secure_files_spec.rb
+++ b/spec/requests/api/ci/secure_files_spec.rb
@@ -56,6 +56,26 @@ RSpec.describe API::Ci::SecureFiles, feature_category: :mobile_devops do
end
end
+ context 'when the feature is disabled at the instance level' do
+ before do
+ stub_config(ci_secure_files: { enabled: false })
+ end
+
+ it 'returns a 403 when attempting to upload a file' do
+ expect do
+ post api("/projects/#{project.id}/secure_files", maintainer), params: file_params
+ end.not_to change { project.secure_files.count }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns a 403 when downloading a file' do
+ get api("/projects/#{project.id}/secure_files", developer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
context 'when the flag is disabled' do
it 'returns a 201 when uploading a file when the ci_secure_files_read_only feature flag is disabled' do
expect do
diff --git a/spec/requests/api/clusters/agent_tokens_spec.rb b/spec/requests/api/clusters/agent_tokens_spec.rb
index 2647684c9f8..c18ebf7d044 100644
--- a/spec/requests/api/clusters/agent_tokens_spec.rb
+++ b/spec/requests/api/clusters/agent_tokens_spec.rb
@@ -162,6 +162,28 @@ RSpec.describe API::Clusters::AgentTokens, feature_category: :deployment_managem
expect(response).to have_gitlab_http_status(:forbidden)
end
end
+
+ context 'when the active agent tokens limit is reached' do
+ before do
+ # create an additional agent token to make it 2
+ create(:cluster_agent_token, agent: agent)
+ end
+
+ it 'returns a bad request (400) error' do
+ params = {
+ name: 'test-token',
+ description: 'Test description'
+ }
+ post(api("/projects/#{project.id}/cluster_agents/#{agent.id}/tokens", user), params: params)
+
+ aggregate_failures "testing response" do
+ expect(response).to have_gitlab_http_status(:bad_request)
+
+ error_message = json_response['message']
+ expect(error_message).to eq('400 Bad request - An agent can have only two active tokens at a time')
+ end
+ end
+ end
end
describe 'DELETE /projects/:id/cluster_agents/:agent_id/tokens/:token_id' do
diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb
index 030962044c6..b1566860ffc 100644
--- a/spec/requests/api/debian_project_packages_spec.rb
+++ b/spec/requests/api/debian_project_packages_spec.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe API::DebianProjectPackages, feature_category: :package_registry do
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 18a9211df3e..30c345ef458 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -59,6 +59,17 @@ RSpec.describe API::DeployKeys, :aggregate_failures, feature_category: :continuo
expect { make_api_request }.not_to exceed_all_query_limit(control)
end
+ it 'avoids N+1 database queries', :use_sql_query_cache, :request_store do
+ create(:deploy_keys_project, :readonly_access, project: project2, deploy_key: deploy_key)
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { make_api_request }
+
+ deploy_key2 = create(:deploy_key, public: true)
+ create(:deploy_keys_project, :readonly_access, project: project3, deploy_key: deploy_key2)
+
+ expect { make_api_request }.not_to exceed_all_query_limit(control)
+ end
+
context 'when `public` parameter is `true`' do
it 'only returns public deploy keys' do
make_api_request({ public: true })
@@ -81,6 +92,21 @@ RSpec.describe API::DeployKeys, :aggregate_failures, feature_category: :continuo
expect(response_projects_with_write_access[1]['id']).to eq(project3.id)
end
end
+
+ context 'projects_with_readonly_access' do
+ let!(:deploy_keys_project2) { create(:deploy_keys_project, :readonly_access, project: project2, deploy_key: deploy_key) }
+ let!(:deploy_keys_project3) { create(:deploy_keys_project, :readonly_access, project: project3, deploy_key: deploy_key) }
+
+ it 'returns projects with readonly access' do
+ make_api_request
+
+ response_projects_with_readonly_access = json_response.first['projects_with_readonly_access']
+
+ expect(response_projects_with_readonly_access[0]['id']).to eq(project.id)
+ expect(response_projects_with_readonly_access[1]['id']).to eq(project2.id)
+ expect(response_projects_with_readonly_access[2]['id']).to eq(project3.id)
+ end
+ end
end
end
@@ -103,6 +129,7 @@ RSpec.describe API::DeployKeys, :aggregate_failures, feature_category: :continuo
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title)
expect(json_response.first).not_to have_key(:projects_with_write_access)
+ expect(json_response.first).not_to have_key(:projects_with_readonly_access)
end
it 'returns multiple deploy keys without N + 1' do
@@ -129,6 +156,7 @@ RSpec.describe API::DeployKeys, :aggregate_failures, feature_category: :continuo
expect(json_response['title']).to eq(deploy_key.title)
expect(json_response).not_to have_key(:projects_with_write_access)
+ expect(json_response).not_to have_key(:projects_with_readonly_access)
end
it 'returns 404 Not Found with invalid ID' do
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index 3ca54cd40d0..d7056adfcb6 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -38,19 +38,17 @@ RSpec.describe API::Deployments, feature_category: :continuous_delivery do
end
context 'with updated_at filters specified' do
- it 'returns projects deployments with last update in specified datetime range' do
- perform_request({ updated_before: 30.minutes.ago, updated_after: 90.minutes.ago, order_by: :updated_at })
+ context 'when using `order_by=updated_at`' do
+ it 'returns projects deployments with last update in specified datetime range' do
+ perform_request({ updated_before: 30.minutes.ago, updated_after: 90.minutes.ago, order_by: :updated_at })
- 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
-
- context 'when forbidden order_by is specified' do
- before do
- stub_feature_flags(deployments_raise_updated_at_inefficient_error_override: false)
+ 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 'when not using `order_by=updated_at`' do
it 'returns an error' do
perform_request({ updated_before: 30.minutes.ago, updated_after: 90.minutes.ago, order_by: :id })
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index 888220c2251..8a21abf02e2 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'doorkeeper access', feature_category: :system_access do
end
include_examples 'user login request with unique ip limit' do
- def request
+ def gitlab_request
get api('/user'), params: { access_token: token.plaintext_token }
end
end
@@ -34,7 +34,7 @@ RSpec.describe 'doorkeeper access', feature_category: :system_access do
end
include_examples 'user login request with unique ip limit' do
- def request
+ def gitlab_request
get api('/user', user)
end
end
diff --git a/spec/requests/api/error_tracking/collector_spec.rb b/spec/requests/api/error_tracking/collector_spec.rb
deleted file mode 100644
index 6a3e71bc859..00000000000
--- a/spec/requests/api/error_tracking/collector_spec.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-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) }
-
- RSpec.shared_examples 'not found' do
- it 'reponds with 404' do
- subject
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- RSpec.shared_examples 'bad request' do
- it 'responds with 400' do
- subject
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- RSpec.shared_examples 'successful request' do
- it 'writes to the database and returns OK', :aggregate_failures do
- expect { subject }.to change { ErrorTracking::ErrorEvent.count }.by(1)
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- describe "POST /error_tracking/collector/api/:id/envelope" do
- let_it_be(:raw_event) { fixture_file('error_tracking/event.txt') }
- let_it_be(:url) { "/error_tracking/collector/api/#{project.id}/envelope" }
-
- let(:params) { raw_event }
- let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}" } }
-
- subject { post api(url), params: params, headers: headers }
-
- it_behaves_like 'successful request'
-
- context 'intergrated error tracking feature flag is disabled' do
- before do
- stub_feature_flags(integrated_error_tracking: false)
- end
-
- it_behaves_like 'not found'
- end
-
- context 'error tracking feature is disabled' do
- before do
- setting.update!(enabled: false)
- end
-
- it_behaves_like 'not found'
- end
-
- context 'integrated error tracking is disabled' do
- before do
- setting.update!(integrated: false)
- end
-
- it_behaves_like 'not found'
- end
-
- context 'auth headers are missing' do
- let(:headers) { {} }
-
- it_behaves_like 'bad request'
- end
-
- context 'public key is wrong' do
- let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=glet_1fedb514e17f4b958435093deb02048c" } }
-
- it_behaves_like 'not found'
- end
-
- context 'public key is inactive' do
- let(:client_key) { create(:error_tracking_client_key, :disabled, project: project) }
-
- it_behaves_like 'not found'
- end
-
- context 'empty body' do
- let(:params) { '' }
-
- it_behaves_like 'bad request'
- end
-
- context 'unknown request type' do
- let(:params) { fixture_file('error_tracking/unknown.txt') }
-
- it_behaves_like 'bad request'
- end
-
- context 'transaction request type' do
- let(:params) { fixture_file('error_tracking/transaction.txt') }
-
- it 'does nothing and returns ok' do
- expect { subject }.not_to change { ErrorTracking::ErrorEvent.count }
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'gzip body' do
- let(:standard_headers) do
- {
- 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}",
- 'HTTP_CONTENT_ENCODING' => 'gzip'
- }
- end
-
- let(:params) { ActiveSupport::Gzip.compress(raw_event) }
-
- context 'with application/x-sentry-envelope Content-Type' do
- let(:headers) { standard_headers.merge({ 'CONTENT_TYPE' => 'application/x-sentry-envelope' }) }
-
- it_behaves_like 'successful request'
- end
-
- context 'with unexpected Content-Type' do
- let(:headers) { standard_headers.merge({ 'CONTENT_TYPE' => 'application/gzip' }) }
-
- it 'responds with 415' do
- subject
-
- expect(response).to have_gitlab_http_status(:unsupported_media_type)
- end
- end
- end
- end
-
- describe "POST /error_tracking/collector/api/:id/store" do
- let_it_be(:raw_event) { fixture_file('error_tracking/parsed_event.json') }
- let_it_be(:url) { "/error_tracking/collector/api/#{project.id}/store" }
-
- let(:params) { raw_event }
- let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}" } }
-
- subject { post api(url), params: params, headers: headers }
-
- it_behaves_like 'successful request'
-
- context 'empty headers' do
- let(:headers) { {} }
-
- it_behaves_like 'bad request'
- end
-
- context 'empty body' do
- let(:params) { '' }
-
- it_behaves_like 'bad request'
- end
-
- context 'body with string instead of json' do
- let(:params) { '"********"' }
-
- it_behaves_like 'bad request'
- end
-
- context 'collector fails with validation error' do
- before do
- allow(::ErrorTracking::CollectErrorService)
- .to receive(:new).and_raise(Gitlab::ErrorTracking::ErrorRepository::DatabaseError)
- end
-
- it_behaves_like 'bad request'
- end
-
- context 'with platform field too long' do
- let(:params) do
- event = Gitlab::Json.parse(raw_event)
- event['platform'] = 'a' * 256
- Gitlab::Json.dump(event)
- end
-
- it_behaves_like 'bad request'
- end
-
- context 'gzip body' do
- let(:headers) do
- {
- 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}",
- 'HTTP_CONTENT_ENCODING' => 'gzip',
- 'CONTENT_TYPE' => 'application/json'
- }
- end
-
- let(:params) { ActiveSupport::Gzip.compress(raw_event) }
-
- it_behaves_like 'successful request'
- end
-
- context 'body contains nullbytes' do
- let_it_be(:raw_event) { fixture_file('error_tracking/parsed_event_nullbytes.json') }
-
- it_behaves_like 'successful request'
- end
-
- context 'when JSON key transaction is empty string' do
- let_it_be(:raw_event) { fixture_file('error_tracking/php_empty_transaction.json') }
-
- it_behaves_like 'successful request'
- end
-
- context 'sentry_key as param and empty headers' do
- let(:url) { "/error_tracking/collector/api/#{project.id}/store?sentry_key=#{sentry_key}" }
- let(:headers) { {} }
-
- context 'key is wrong' do
- let(:sentry_key) { 'glet_1fedb514e17f4b958435093deb02048c' }
-
- it_behaves_like 'not found'
- end
-
- context 'key is empty' do
- let(:sentry_key) { '' }
-
- it_behaves_like 'bad request'
- end
-
- context 'key is correct' do
- let(:sentry_key) { client_key.public_key }
-
- it_behaves_like 'successful request'
- end
- end
- end
-end
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 9f1af746080..2571e3b1e6a 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -31,6 +31,8 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
describe 'GET /features' do
+ let(:path) { '/features' }
+
let(:expected_features) do
[
{
@@ -74,28 +76,28 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
Feature.enable(known_feature_flag.name)
end
+ it_behaves_like 'GET request permissions for admin mode'
+
it 'returns a 401 for anonymous users' do
get api('/features')
expect(response).to have_gitlab_http_status(:unauthorized)
end
- it 'returns a 403 for users' do
- get api('/features', user)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
it 'returns the feature list for admins' do
- get api('/features', admin)
+ get api('/features', admin, admin_mode: true)
- expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to match_array(expected_features)
end
end
describe 'POST /feature' do
let(:feature_name) { known_feature_flag.name }
+ let(:path) { "/features/#{feature_name}" }
+
+ it_behaves_like 'POST request permissions for admin mode' do
+ let(:params) { { value: 'true' } }
+ end
# TODO: remove this shared examples block when set_feature_flag_service feature flag
# is removed. Then remove also any duplicate specs covered by the service class.
@@ -115,7 +117,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
context 'when passed value=true' do
it 'creates an enabled feature' do
- post api("/features/#{feature_name}", admin), params: { value: 'true' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -129,11 +131,11 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
it 'logs the event' do
expect(Feature.logger).to receive(:info).once
- post api("/features/#{feature_name}", admin), params: { value: 'true' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true' }
end
it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do
- post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true', feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -148,7 +150,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'creates an enabled feature for the given user when passed user=username' do
- post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true', user: user.username }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -163,7 +165,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do
- post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username, feature_group: 'perf_team' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true', user: user.username, feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(feature_name)
@@ -181,7 +183,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
let(:expected_inexistent_path) { actor_path }
it 'returns the current state of the flag without changes' do
- post api("/features/#{feature_name}", admin), params: { value: 'true', actor_type => actor_path }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true', actor_type => actor_path }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq("400 Bad request - #{expected_inexistent_path} is not found!")
@@ -190,7 +192,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
shared_examples 'enables the flag for the actor' do |actor_type|
it 'sets the feature gate' do
- post api("/features/#{feature_name}", admin), params: { value: 'true', actor_type => actor.full_path }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true', actor_type => actor.full_path }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -207,7 +209,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
shared_examples 'creates an enabled feature for the specified entries' do
it do
- post api("/features/#{feature_name}", admin), params: { value: 'true', **gate_params }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true', **gate_params }
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(feature_name)
@@ -404,7 +406,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'creates a feature with the given percentage of time if passed an integer' do
- post api("/features/#{feature_name}", admin), params: { value: '50' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '50' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -419,7 +421,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'creates a feature with the given percentage of time if passed a float' do
- post api("/features/#{feature_name}", admin), params: { value: '0.01' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '0.01' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -434,7 +436,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'creates a feature with the given percentage of actors if passed an integer' do
- post api("/features/#{feature_name}", admin), params: { value: '50', key: 'percentage_of_actors' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '50', key: 'percentage_of_actors' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -449,7 +451,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'creates a feature with the given percentage of actors if passed a float' do
- post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '0.01', key: 'percentage_of_actors' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -473,7 +475,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
context 'when key and feature_group are provided' do
before do
- post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', feature_group: 'some-value' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '0.01', key: 'percentage_of_actors', feature_group: 'some-value' }
end
it_behaves_like 'fails to set the feature flag'
@@ -481,7 +483,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
context 'when key and user are provided' do
before do
- post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', user: 'some-user' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '0.01', key: 'percentage_of_actors', user: 'some-user' }
end
it_behaves_like 'fails to set the feature flag'
@@ -489,7 +491,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
context 'when key and group are provided' do
before do
- post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', group: 'somepath' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '0.01', key: 'percentage_of_actors', group: 'somepath' }
end
it_behaves_like 'fails to set the feature flag'
@@ -497,7 +499,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
context 'when key and namespace are provided' do
before do
- post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', namespace: 'somepath' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '0.01', key: 'percentage_of_actors', namespace: 'somepath' }
end
it_behaves_like 'fails to set the feature flag'
@@ -505,7 +507,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
context 'when key and project are provided' do
before do
- post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors', project: 'somepath' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '0.01', key: 'percentage_of_actors', project: 'somepath' }
end
it_behaves_like 'fails to set the feature flag'
@@ -520,7 +522,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
context 'when passed value=true' do
it 'enables the feature' do
- post api("/features/#{feature_name}", admin), params: { value: 'true' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -532,7 +534,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do
- post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true', feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -547,7 +549,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'enables the feature for the given user when passed user=username' do
- post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true', user: user.username }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -567,7 +569,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
Feature.enable(feature_name)
expect(Feature.enabled?(feature_name)).to eq(true)
- post api("/features/#{feature_name}", admin), params: { value: 'false' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'false' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -582,7 +584,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
Feature.enable(feature_name, Feature.group(:perf_team))
expect(Feature.enabled?(feature_name, admin)).to be_truthy
- post api("/features/#{feature_name}", admin), params: { value: 'false', feature_group: 'perf_team' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'false', feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -597,7 +599,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
Feature.enable(feature_name, user)
expect(Feature.enabled?(feature_name, user)).to be_truthy
- post api("/features/#{feature_name}", admin), params: { value: 'false', user: user.username }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'false', user: user.username }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -615,7 +617,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'updates the percentage of time if passed an integer' do
- post api("/features/#{feature_name}", admin), params: { value: '30' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '30' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -636,7 +638,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'updates the percentage of actors if passed an integer' do
- post api("/features/#{feature_name}", admin), params: { value: '74', key: 'percentage_of_actors' }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: '74', key: 'percentage_of_actors' }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to match(
@@ -663,7 +665,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
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 }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'opt_out', user: user.username }
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to include(
@@ -683,7 +685,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
end
it 'refuses to enable the feature' do
- post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'true', user: user.username }
expect(Feature).not_to be_enabled(feature_name, user)
@@ -702,7 +704,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
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 }
+ post api("/features/#{feature_name}", admin, admin_mode: true), params: { value: 'opt_out', user: user.username }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -711,6 +713,9 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
describe 'DELETE /feature/:name' do
let(:feature_name) { 'my_feature' }
+ let(:path) { "/features/#{feature_name}" }
+
+ it_behaves_like 'DELETE request permissions for admin mode'
context 'when the user has no access' do
it 'returns a 401 for anonymous users' do
@@ -728,7 +733,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
context 'when the user has access' do
it 'returns 204 when the value is not set' do
- delete api("/features/#{feature_name}", admin)
+ delete api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:no_content)
end
@@ -740,7 +745,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
it 'deletes an enabled feature' do
expect do
- delete api("/features/#{feature_name}", admin)
+ delete api("/features/#{feature_name}", admin, admin_mode: true)
Feature.reset
end.to change { Feature.persisted_name?(feature_name) }
.and change { Feature.enabled?(feature_name) }
@@ -751,7 +756,7 @@ RSpec.describe API::Features, stub_feature_flags: false, feature_category: :feat
it 'logs the event' do
expect(Feature.logger).to receive(:info).once
- delete api("/features/#{feature_name}", admin)
+ delete api("/features/#{feature_name}", admin, admin_mode: true)
end
end
end
diff --git a/spec/requests/api/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb
index 6b3f378a4bc..9e8bfab6468 100644
--- a/spec/requests/api/generic_packages_spec.rb
+++ b/spec/requests/api/generic_packages_spec.rb
@@ -276,9 +276,9 @@ RSpec.describe API::GenericPackages, feature_category: :package_registry do
expect(package.version).to eq('0.0.1')
if should_set_build_info
- expect(package.original_build_info.pipeline).to eq(ci_build.pipeline)
+ expect(package.last_build_info.pipeline).to eq(ci_build.pipeline)
else
- expect(package.original_build_info).to be_nil
+ expect(package.last_build_info).to be_nil
end
package_file = package.package_files.last
diff --git a/spec/requests/api/graphql/audit_events/definitions_spec.rb b/spec/requests/api/graphql/audit_events/definitions_spec.rb
new file mode 100644
index 00000000000..4e0f4dcfae1
--- /dev/null
+++ b/spec/requests/api/graphql/audit_events/definitions_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting a list of audit event definitions', feature_category: :audit_events do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+
+ let(:path) { %i[audit_event_definitions nodes] }
+ let(:audit_event_definition_keys) do
+ Gitlab::Audit::Type::Definition.definitions.keys
+ end
+
+ let(:query) { graphql_query_for(:audit_event_definitions, {}, 'nodes { name }') }
+
+ it 'returns the audit event definitions' do
+ post_graphql(query, current_user: current_user)
+
+ returned_names = graphql_data_at(*path).map { |v| v['name'].to_sym }
+
+ expect(returned_names).to all be_in(audit_event_definition_keys)
+ end
+end
diff --git a/spec/requests/api/graphql/ci/group_environment_scopes_spec.rb b/spec/requests/api/graphql/ci/group_environment_scopes_spec.rb
new file mode 100644
index 00000000000..13a3a128979
--- /dev/null
+++ b/spec/requests/api/graphql/ci/group_environment_scopes_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.group(fullPath).environmentScopes', feature_category: :secrets_management do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let(:expected_environment_scopes) do
+ %w[
+ group1_environment1
+ group1_environment2
+ group2_environment3
+ group2_environment4
+ group2_environment5
+ group2_environment6
+ ]
+ end
+
+ let(:query) do
+ %(
+ query {
+ group(fullPath: "#{group.full_path}") {
+ environmentScopes#{environment_scopes_params} {
+ nodes {
+ name
+ }
+ }
+ }
+ }
+ )
+ end
+
+ before do
+ group.add_developer(user)
+ expected_environment_scopes.each_with_index do |env, index|
+ create(:ci_group_variable, group: group, key: "var#{index + 1}", environment_scope: env)
+ end
+ end
+
+ context 'when query has no parameters' do
+ let(:environment_scopes_params) { "" }
+
+ it 'returns all avaiable environment scopes' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('group', 'environmentScopes', 'nodes')).to eq(
+ expected_environment_scopes.map { |env_scope| { 'name' => env_scope } }
+ )
+ end
+ end
+
+ context 'when query has search parameters' do
+ let(:environment_scopes_params) { "(search: \"group1\")" }
+
+ it 'returns only environment scopes with group1 prefix' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('group', 'environmentScopes', 'nodes')).to eq(
+ [
+ { 'name' => 'group1_environment1' },
+ { 'name' => 'group1_environment2' }
+ ]
+ )
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb
index 0d5ac725edd..756fcd8b7cd 100644
--- a/spec/requests/api/graphql/ci/jobs_spec.rb
+++ b/spec/requests/api/graphql/ci/jobs_spec.rb
@@ -183,7 +183,7 @@ RSpec.describe 'Query.project.pipeline', feature_category: :continuous_integrati
#{all_graphql_fields_for('CiBuildNeed')}
}
... on CiJob {
- #{all_graphql_fields_for('CiJob')}
+ #{all_graphql_fields_for('CiJob', excluded: %w[aiFailureAnalysis])}
}
}
}
@@ -433,8 +433,6 @@ RSpec.describe 'Query.project.pipeline', feature_category: :continuous_integrati
end
it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
- admin2 = create(:admin)
-
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: admin)
end
@@ -442,7 +440,7 @@ RSpec.describe 'Query.project.pipeline', feature_category: :continuous_integrati
runner_manager2 = create(:ci_runner_machine)
create(:ci_build, pipeline: pipeline, name: 'my test job2', runner_manager: runner_manager2)
- expect { post_graphql(query, current_user: admin2) }.not_to exceed_all_query_limit(control)
+ expect { post_graphql(query, current_user: admin) }.not_to exceed_all_query_limit(control)
end
end
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 52b548ce8b9..63a657f3962 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -74,6 +74,8 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
end
it 'retrieves expected fields' do
+ stub_commonmark_sourcepos_disabled
+
post_graphql(query, current_user: user)
runner_data = graphql_data_at(:runner)
diff --git a/spec/requests/api/graphql/ci/stages_spec.rb b/spec/requests/api/graphql/ci/stages_spec.rb
index f4e1a69d455..2d646a0e1c3 100644
--- a/spec/requests/api/graphql/ci/stages_spec.rb
+++ b/spec/requests/api/graphql/ci/stages_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'Query.project.pipeline.stages', feature_category: :continuous_in
let(:fields) do
<<~QUERY
nodes {
- #{all_graphql_fields_for('CiStage')}
+ #{all_graphql_fields_for('CiStage', max_depth: 2)}
}
QUERY
end
@@ -37,7 +37,7 @@ RSpec.describe 'Query.project.pipeline.stages', feature_category: :continuous_in
before_all do
create(:ci_stage, pipeline: pipeline, name: 'deploy')
- create_list(:ci_build, 2, pipeline: pipeline, stage: 'deploy')
+ create(:ci_build, pipeline: pipeline, stage: 'deploy')
end
it_behaves_like 'a working graphql query' do
@@ -58,7 +58,7 @@ RSpec.describe 'Query.project.pipeline.stages', feature_category: :continuous_in
it 'returns up to default limit jobs per stage' do
post_query
- expect(job_nodes.count).to eq(2)
+ expect(job_nodes.count).to eq(1)
end
context 'when the limit is manually set' do
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 151d07ff0a7..435e5e62f69 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', feature_category: :subgroups do
+RSpec.describe 'Query current user groups', feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
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 a6eb114a279..961de84234c 100644
--- a/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb
+++ b/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb
@@ -27,6 +27,7 @@ RSpec.describe 'getting dependency proxy blobs in a group', feature_category: :d
dependencyProxyBlobCount
dependencyProxyTotalSize
dependencyProxyTotalSizeInBytes
+ dependencyProxyTotalSizeBytes
GQL
end
@@ -132,4 +133,49 @@ RSpec.describe 'getting dependency proxy blobs in a group', feature_category: :d
expected_size = blobs.inject(0) { |sum, blob| sum + blob.size }
expect(dependency_proxy_total_size_in_bytes_response).to eq(expected_size)
end
+
+ context 'with a giant size blob' do
+ let_it_be(:owner) { create(:user) }
+ let_it_be_with_reload(:group) { create(:group) }
+ let_it_be(:blob) do
+ create(:dependency_proxy_blob, file_name: 'blob2.json', group: group, size: GraphQL::Types::Int::MAX + 1)
+ end
+
+ let_it_be(:blobs) { [blob].flatten }
+
+ context 'using dependencyProxyTotalSizeInBytes' do
+ let(:fields) do
+ <<~GQL
+ #{query_graphql_field('dependency_proxy_blobs', {}, dependency_proxy_blob_fields)}
+ dependencyProxyTotalSizeInBytes
+ GQL
+ end
+
+ it 'returns an error' do
+ post_graphql(query, current_user: user, variables: variables)
+
+ err_message = 'Integer out of bounds'
+ expect(graphql_errors).to include(a_hash_including('message' => a_string_including(err_message)))
+ end
+ end
+
+ context 'using dependencyProxyTotalSizeBytes' do
+ let(:fields) do
+ <<~GQL
+ #{query_graphql_field('dependency_proxy_blobs', {}, dependency_proxy_blob_fields)}
+ dependencyProxyTotalSizeBytes
+ GQL
+ end
+
+ let(:dependency_proxy_total_size_bytes_response) { graphql_data.dig('group', 'dependencyProxyTotalSizeBytes') }
+
+ it 'returns the total size in bytes as a string' do
+ post_graphql(query, current_user: user, variables: variables)
+
+ expect(graphql_errors).to be_nil
+ expected_size = String(blobs.inject(0) { |sum, blob| sum + blob.size })
+ expect(dependency_proxy_total_size_bytes_response).to eq(expected_size)
+ end
+ end
+ end
end
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 aca8527ba0a..c8745fcbb62 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
@@ -46,12 +46,15 @@ RSpec.describe 'getting dependency proxy settings for a group', feature_category
context 'with different permissions' do
where(:group_visibility, :role, :access_granted) do
- :private | :maintainer | true
+ :private | :owner | true
+ :private | :maintainer | false
:private | :developer | false
:private | :reporter | false
:private | :guest | false
:private | :anonymous | false
- :public | :maintainer | true
+
+ :public | :owner | true
+ :public | :maintainer | false
:public | :developer | false
:public | :reporter | false
:public | :guest | false
@@ -73,6 +76,20 @@ RSpec.describe 'getting dependency proxy settings for a group', feature_category
expect(dependency_proxy_group_setting_response).to be_blank
end
end
+
+ context 'with disabled admin_package feature flag' do
+ before do
+ stub_feature_flags(raise_group_admin_package_permission_to_owner: false)
+ end
+
+ if params[:role] == :maintainer
+ it 'return the proper response' do
+ subject
+
+ expect(dependency_proxy_group_setting_response).to eq('enabled' => true)
+ end
+ end
+ end
end
end
end
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 edff4dc1dae..8365cece4a3 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
@@ -45,12 +45,15 @@ RSpec.describe 'getting dependency proxy image ttl policy for a group', feature_
context 'with different permissions' do
where(:group_visibility, :role, :access_granted) do
- :private | :maintainer | true
+ :private | :owner | true
+ :private | :maintainer | false
:private | :developer | false
:private | :reporter | false
:private | :guest | false
:private | :anonymous | false
- :public | :maintainer | true
+
+ :public | :owner | true
+ :public | :maintainer | false
:public | :developer | false
:public | :reporter | false
:public | :guest | false
@@ -72,6 +75,20 @@ RSpec.describe 'getting dependency proxy image ttl policy for a group', feature_
expect(dependency_proxy_image_ttl_policy_response).to be_blank
end
end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(raise_group_admin_package_permission_to_owner: false)
+ end
+
+ if params[:role] == :maintainer
+ it 'returns the proper response' do
+ subject
+
+ expect(dependency_proxy_image_ttl_policy_response).to eq("createdAt" => nil, "enabled" => false, "ttl" => 90, "updatedAt" => nil)
+ end
+ end
+ end
end
end
end
diff --git a/spec/requests/api/graphql/group/group_members_spec.rb b/spec/requests/api/graphql/group/group_members_spec.rb
index 26d1fb48408..e56e901466a 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', feature_category: :subgroups do
+RSpec.describe 'getting group members information', feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(:parent_group) { create(:group, :public) }
diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb
index ce5816999a6..6debe2d3d67 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', :with_license, feature_category: :subgroups do
+RSpec.describe 'getting group information', :with_license, feature_category: :groups_and_projects do
include GraphqlHelpers
include UploadHelpers
diff --git a/spec/requests/api/graphql/groups_query_spec.rb b/spec/requests/api/graphql/groups_query_spec.rb
index 84c8d3c3388..460cb40b68a 100644
--- a/spec/requests/api/graphql/groups_query_spec.rb
+++ b/spec/requests/api/graphql/groups_query_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'searching groups', :with_license, feature_category: :subgroups do
+RSpec.describe 'searching groups', :with_license, feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/requests/api/graphql/jobs_query_spec.rb b/spec/requests/api/graphql/jobs_query_spec.rb
index 7607aeac6e0..4248a03fa74 100644
--- a/spec/requests/api/graphql/jobs_query_spec.rb
+++ b/spec/requests/api/graphql/jobs_query_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'getting job information', feature_category: :continuous_integrat
:jobs, {}, %(
count
nodes {
- #{all_graphql_fields_for(::Types::Ci::JobType, max_depth: 1)}
+ #{all_graphql_fields_for(::Types::Ci::JobType, max_depth: 1, excluded: %w[aiFailureAnalysis])}
})
)
end
diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb
index 7d1850b1b93..b973e7d4d51 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', feature_category: :projects do
+RSpec.describe 'getting project information', feature_category: :groups_and_projects do
include GraphqlHelpers
let(:query) { graphql_query_for('metadata', {}, all_graphql_fields_for('Metadata')) }
diff --git a/spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb b/spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb
new file mode 100644
index 00000000000..f759e6dce08
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Achievements::DeleteUserAchievement, feature_category: :user_profile do
+ include GraphqlHelpers
+
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:owner) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:achievement) { create(:achievement, namespace: group) }
+ let_it_be(:user_achievement) { create(:user_achievement, achievement: achievement) }
+
+ let(:mutation) { graphql_mutation(:user_achievements_delete, params) }
+ let(:user_achievement_id) { user_achievement&.to_global_id }
+ let(:params) { { user_achievement_id: user_achievement_id } }
+
+ subject { post_graphql_mutation(mutation, current_user: current_user) }
+
+ before_all do
+ group.add_maintainer(maintainer)
+ group.add_owner(owner)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { maintainer }
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+
+ it 'does not delete any user achievements' do
+ expect { subject }.not_to change { Achievements::UserAchievement.count }
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { owner }
+
+ context 'when the params are invalid' do
+ let(:user_achievement) { nil }
+
+ it 'returns the validation error' do
+ subject
+
+ expect(graphql_errors.to_s).to include('invalid value for userAchievementId (Expected value to not be null)')
+ end
+ end
+
+ context 'when the user_achievement_id is invalid' do
+ let(:user_achievement_id) { "gid://gitlab/Achievements::UserAchievement/#{non_existing_record_id}" }
+
+ it 'returns the relevant error' do
+ subject
+
+ expect(graphql_errors.to_s)
+ .to include("The resource that you are attempting to access does not exist or you don't have permission")
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(achievements: false)
+ end
+
+ it 'returns the relevant error' do
+ subject
+
+ expect(graphql_errors.to_s)
+ .to include("The resource that you are attempting to access does not exist or you don't have permission")
+ end
+ end
+
+ context 'when everything is ok' do
+ it 'deletes an user achievement' do
+ expect { subject }.to change { Achievements::UserAchievement.count }.by(-1)
+ end
+
+ it 'returns the deleted user achievement' do
+ subject
+
+ expect(graphql_data_at(:user_achievements_delete, :user_achievement, :achievement, :id))
+ .to eq(achievement.to_global_id.to_s)
+ end
+ end
+ end
+end
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 187c88363c6..b0e9f59b996 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', feature_category: :integrations do
+RSpec.describe 'Creating a new HTTP 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/http_integration/destroy_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/destroy_spec.rb
index 1c77c71daba..110c65d24a0 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', feature_category: :integrations do
+RSpec.describe 'Removing an HTTP Integration', 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/reset_token_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/reset_token_spec.rb
index 427277dd540..049d7e8dace 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', feature_category: :integrations do
+RSpec.describe 'Resetting a token on an existing HTTP Integration', 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/update_spec.rb b/spec/requests/api/graphql/mutations/alert_management/http_integration/update_spec.rb
index a9d189d564d..70adff1fdc4 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', feature_category: :integrations do
+RSpec.describe 'Updating an existing HTTP Integration', feature_category: :incident_management do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb
index 4e25669a0ca..5cb48ec44a0 100644
--- a/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb
@@ -41,23 +41,6 @@ RSpec.describe 'BulkDestroy', feature_category: :build_artifacts do
expect(first_artifact.reload).to be_persisted
end
- context 'when the `ci_job_artifact_bulk_destroy` feature flag is disabled' do
- before do
- stub_feature_flags(ci_job_artifact_bulk_destroy: false)
- project.add_maintainer(maintainer)
- end
-
- it 'returns a resource not available error' do
- post_graphql_mutation(mutation, current_user: maintainer)
-
- expect(graphql_errors).to contain_exactly(
- hash_including(
- 'message' => '`ci_job_artifact_bulk_destroy` feature flag is disabled.'
- )
- )
- end
- end
-
context "when the user is a developer in a project" do
before do
project.add_developer(developer)
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 aa00069b241..fd92ed198e7 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
@@ -64,35 +64,6 @@ RSpec.describe 'ProjectCiCdSettingsUpdate', feature_category: :continuous_integr
expect(project.keep_latest_artifact).to eq(false)
end
- describe 'ci_cd_settings_update deprecated mutation' do
- let(:mutation) { graphql_mutation(:ci_cd_settings_update, variables) }
-
- it 'returns error' do
- post_graphql_mutation(mutation, current_user: user)
-
- expect(graphql_errors).to(
- include(
- hash_including('message' => '`remove_cicd_settings_update` feature flag is enabled.')
- )
- )
- end
-
- context 'when remove_cicd_settings_update FF is disabled' do
- before do
- stub_feature_flags(remove_cicd_settings_update: false)
- end
-
- it 'updates ci cd settings' do
- post_graphql_mutation(mutation, current_user: user)
-
- project.reload
-
- expect(response).to have_gitlab_http_status(:success)
- expect(project.keep_latest_artifact).to eq(false)
- end
- end
- end
-
it 'allows setting job_token_scope_enabled to false' do
post_graphql_mutation(mutation, current_user: user)
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 5d5696d3f66..86ea77a8f35 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
@@ -49,16 +49,21 @@ RSpec.describe 'Updating the dependency proxy group settings', feature_category:
end
context 'with permission' do
- before do
- group.add_maintainer(user)
- end
+ %i[owner maintainer].each do |role|
+ context "for #{role}" do
+ before do
+ group.send("add_#{role}", user)
+ stub_feature_flags(raise_group_admin_package_permission_to_owner: false)
+ end
- it 'returns the updated dependency proxy settings', :aggregate_failures do
- subject
+ it 'returns the updated dependency proxy settings', :aggregate_failures do
+ subject
- expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['errors']).to be_empty
- expect(group_settings[:enabled]).to eq(false)
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to be_empty
+ expect(group_settings[:enabled]).to eq(false)
+ end
+ end
end
end
end
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 66ee17f356c..bc8b2da84b9 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
@@ -51,19 +51,24 @@ RSpec.describe 'Updating the dependency proxy image ttl policy', feature_categor
end
context 'with permission' do
- before do
- group.add_maintainer(user)
- end
+ %i[owner maintainer].each do |role|
+ context "for #{role}" do
+ before do
+ group.send("add_#{role}", user)
+ stub_feature_flags(raise_group_admin_package_permission_to_owner: false)
+ end
- it 'returns the updated dependency proxy image ttl policy', :aggregate_failures do
- subject
+ it 'returns the updated dependency proxy image ttl policy', :aggregate_failures do
+ subject
- expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['errors']).to be_empty
- expect(ttl_policy_response).to include(
- 'enabled' => params[:enabled],
- 'ttl' => params[:ttl]
- )
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to be_empty
+ expect(ttl_policy_response).to include(
+ 'enabled' => params[:enabled],
+ 'ttl' => params[:ttl]
+ )
+ end
+ end
end
end
end
diff --git a/spec/requests/api/graphql/mutations/environments/create_spec.rb b/spec/requests/api/graphql/mutations/environments/create_spec.rb
new file mode 100644
index 00000000000..8a67f86dc4b
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/environments/create_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Create Environment', feature_category: :environment_management do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
+
+ let(:current_user) { developer }
+
+ let(:mutation) do
+ graphql_mutation(:environment_create, input)
+ end
+
+ context 'when creating an environment' do
+ let(:input) do
+ {
+ project_path: project.full_path,
+ name: 'production',
+ external_url: 'https://gitlab.com/'
+ }
+ end
+
+ it 'creates successfully' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_mutation_response(:environment_create)['environment']['name']).to eq('production')
+ expect(graphql_mutation_response(:environment_create)['environment']['externalUrl']).to eq('https://gitlab.com/')
+ expect(graphql_mutation_response(:environment_create)['errors']).to be_empty
+ end
+
+ context 'when current user is reporter' do
+ let(:current_user) { reporter }
+
+ it 'returns error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors.to_s)
+ .to include("The resource that you are attempting to access does not exist or you don't have permission")
+ end
+ end
+ end
+
+ context 'when name is missing' do
+ let(:input) do
+ {
+ project_path: project.full_path
+ }
+ end
+
+ it 'returns error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors.to_s).to include("Expected value to not be null")
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/environments/delete_spec.rb b/spec/requests/api/graphql/mutations/environments/delete_spec.rb
new file mode 100644
index 00000000000..1e28d0ebc0b
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/environments/delete_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Delete Environment', feature_category: :deployment_management do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:environment) { create(:environment, project: project, state: :stopped) }
+ let_it_be(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_maintainer(u) } }
+
+ let(:environment_id) { environment.to_global_id.to_s }
+ let(:current_user) { developer }
+
+ let(:mutation) do
+ graphql_mutation(:environment_delete, input)
+ end
+
+ context 'when delete is successful' do
+ let(:input) do
+ { id: environment_id }
+ end
+
+ it 'deletes the environment' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { project.reload.environments.include?(environment) }.from(true).to(false)
+
+ expect(graphql_mutation_response(:environment_delete)['errors']).to be_empty
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/environments/update_spec.rb b/spec/requests/api/graphql/mutations/environments/update_spec.rb
new file mode 100644
index 00000000000..9c68b3a024c
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/environments/update_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Update Environment', feature_category: :deployment_management do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_maintainer(u) } }
+
+ let(:environment_id) { environment.to_global_id.to_s }
+ let(:current_user) { developer }
+
+ let(:mutation) do
+ graphql_mutation(:environment_update, input)
+ end
+
+ context 'when updating external URL' do
+ let(:input) do
+ {
+ id: environment_id,
+ external_url: 'https://gitlab.com/'
+ }
+ end
+
+ it 'updates successfully' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { environment.reload.external_url }.to('https://gitlab.com/')
+
+ expect(graphql_mutation_response(:environment_update)['errors']).to be_empty
+ end
+
+ context 'when url is invalid' do
+ let(:input) do
+ {
+ id: environment_id,
+ external_url: 'http://${URL}'
+ }
+ end
+
+ it 'returns error' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { environment.reload.external_url }
+
+ expect(graphql_mutation_response(:environment_update)['errors'].first).to include('URI is invalid')
+ end
+ end
+ end
+
+ context 'when updating tier' do
+ let(:input) do
+ {
+ id: environment_id,
+ tier: 'STAGING'
+ }
+ end
+
+ it 'updates successfully' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { environment.reload.tier }.to('staging')
+
+ expect(graphql_mutation_response(:environment_update)['errors']).to be_empty
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/groups/update_spec.rb b/spec/requests/api/graphql/mutations/groups/update_spec.rb
index a9acc593229..b75b2464c22 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', feature_category: :subgroups do
+RSpec.describe 'GroupUpdate', feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(: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 ab15aa97680..58659ea0824 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', feature_category: :integrations do
+RSpec.describe 'Importing Jira Users', feature_category: :importers 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 a864bc88afc..fc4a1488b27 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', feature_category: :integrations do
+RSpec.describe 'Starting a Jira Import', feature_category: :importers do
include JiraIntegrationHelpers
include GraphqlHelpers
diff --git a/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb b/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb
index f15b52f53a3..1395f7b778f 100644
--- a/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb
+++ b/spec/requests/api/graphql/mutations/members/groups/bulk_update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'GroupMemberBulkUpdate', feature_category: :subgroups do
+RSpec.describe 'GroupMemberBulkUpdate', feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(:parent_group) { create(:group) }
diff --git a/spec/requests/api/graphql/mutations/members/projects/bulk_update_spec.rb b/spec/requests/api/graphql/mutations/members/projects/bulk_update_spec.rb
index cbef9715cbe..910e512f6d6 100644
--- a/spec/requests/api/graphql/mutations/members/projects/bulk_update_spec.rb
+++ b/spec/requests/api/graphql/mutations/members/projects/bulk_update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe 'ProjectMemberBulkUpdate', feature_category: :projects do
+RSpec.describe 'ProjectMemberBulkUpdate', feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(:parent_group) { create(:group) }
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 f4f4f34fe29..2f26a2f92b2 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
@@ -139,6 +139,15 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
end
end
+ # To be removed when raise_group_admin_package_permission_to_owner FF is removed
+ RSpec.shared_examples 'disabling admin_package feature flag' do |action:|
+ before do
+ stub_feature_flags(raise_group_admin_package_permission_to_owner: false)
+ end
+
+ it_behaves_like "accepting the mutation request #{action} the package settings"
+ end
+
describe 'post graphql mutation' do
subject { post_graphql_mutation(mutation, current_user: user) }
@@ -147,7 +156,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
let_it_be(:namespace, reload: true) { package_settings.namespace }
where(:user_role, :shared_examples_name) do
- :maintainer | 'accepting the mutation request updating the package settings'
+ :owner | 'accepting the mutation request updating the package settings'
+ :maintainer | 'denying the mutation request'
:developer | 'denying the mutation request'
:reporter | 'denying the mutation request'
:guest | 'denying the mutation request'
@@ -160,6 +170,7 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
end
it_behaves_like params[:shared_examples_name]
+ it_behaves_like 'disabling admin_package feature flag', action: :updating if params[:user_role] == :maintainer
end
end
@@ -169,7 +180,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
let(:package_settings) { namespace.package_settings }
where(:user_role, :shared_examples_name) do
- :maintainer | 'accepting the mutation request creating the package settings'
+ :owner | 'accepting the mutation request creating the package settings'
+ :maintainer | 'denying the mutation request'
:developer | 'denying the mutation request'
:reporter | 'denying the mutation request'
:guest | 'denying the mutation request'
@@ -182,6 +194,7 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis
end
it_behaves_like params[:shared_examples_name]
+ it_behaves_like 'disabling admin_package feature flag', action: :creating if params[:user_role] == :maintainer
end
end
end
diff --git a/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb b/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb
index c5dc6f390d9..0745fb945bb 100644
--- a/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb
+++ b/spec/requests/api/graphql/mutations/projects/sync_fork_spec.rb
@@ -32,24 +32,6 @@ RSpec.describe "Sync project fork", feature_category: :source_code_management do
source_project.change_head('feature')
end
- context 'when synchronize_fork feature flag is disabled' do
- before do
- stub_feature_flags(synchronize_fork: false)
- end
-
- it 'does not call the sync service' do
- expect(::Projects::Forks::SyncWorker).not_to receive(:perform_async)
-
- post_graphql_mutation(mutation, current_user: current_user)
-
- expect(graphql_mutation_response(:project_sync_fork)).to eq(
- {
- 'details' => nil,
- 'errors' => ['Feature flag is disabled']
- })
- end
- end
-
context 'when the branch is protected', :use_clean_rails_redis_caching do
let_it_be(:protected_branch) do
create(:protected_branch, :no_one_can_push, project: project, name: target_branch)
diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
index 0b1af2bf628..a6d727ae6d3 100644
--- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
@@ -103,10 +103,6 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d
end
it_behaves_like 'snippet edit usage data counters'
-
- it_behaves_like 'a mutation which can mutate a spammable' do
- let(:service) { Snippets::CreateService }
- end
end
context 'with PersonalSnippet' do
@@ -165,7 +161,7 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d
it do
expect(::Snippets::CreateService).to receive(:new)
- .with(project: nil, current_user: user, params: hash_including(files: expected_value), spam_params: instance_of(::Spam::SpamParams))
+ .with(project: nil, current_user: user, params: hash_including(files: expected_value))
.and_return(double(execute: creation_response))
subject
@@ -182,7 +178,7 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d
it 'returns an error' do
subject
- expect(json_response['errors']).to be
+ expect(json_response['errors']).to be_present
end
end
end
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index 3b98ee3c2e9..7c5ab691b51 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -110,10 +110,6 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d
end
end
- it_behaves_like 'a mutation which can mutate a spammable' do
- let(:service) { Snippets::UpdateService }
- end
-
def blob_at(filename)
snippet.repository.blob_at('HEAD', filename)
end
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 ce1c2c01faa..60b5795ee9b 100644
--- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
@@ -869,7 +869,7 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do
let_it_be(:issue) { create(:work_item, project: project) }
let_it_be(:link) { create(:parent_link, work_item_parent: issue, work_item: work_item) }
- let(:error_msg) { 'Work item type cannot be changed to Issue with Issue as parent type.' }
+ let(:error_msg) { 'Work item type cannot be changed to issue when linked to a parent issue.' }
it 'does not update the work item type' do
expect do
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 83edacaf831..a4bc94798be 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', feature_category: :projects do
+RSpec.describe 'getting projects', feature_category: :groups_and_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 cee698d6dc5..7c48f324d24 100644
--- a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
+++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
@@ -10,9 +10,11 @@ RSpec.describe 'rendering namespace statistics', feature_category: :metrics do
let(:user) { create(:user) }
let(:query) do
- graphql_query_for('namespace',
- { 'fullPath' => namespace.full_path },
- "rootStorageStatistics { #{all_graphql_fields_for('RootStorageStatistics')} }")
+ graphql_query_for(
+ 'namespace',
+ { 'fullPath' => namespace.full_path },
+ "rootStorageStatistics { #{all_graphql_fields_for('RootStorageStatistics')} }"
+ )
end
shared_examples 'a working namespace with storage statistics query' do
diff --git a/spec/requests/api/graphql/namespace_query_spec.rb b/spec/requests/api/graphql/namespace_query_spec.rb
index d12a3875ebf..c0c7c5fee2b 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', feature_category: :subgroups do
+RSpec.describe 'Query', feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
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 c4843c3cf97..0ca4ec0e363 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', feature_category: :projects do
+RSpec.describe 'getting Alert Management Alert Assignees', feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
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 e8d19513a4e..e48db541e1f 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', feature_category: :integrations do
+RSpec.describe 'getting Alert Management Integrations', feature_category: :incident_management do
include ::Gitlab::Routing
include GraphqlHelpers
diff --git a/spec/requests/api/graphql/project/environments_spec.rb b/spec/requests/api/graphql/project/environments_spec.rb
index bb1763ee228..3a863bd3d77 100644
--- a/spec/requests/api/graphql/project/environments_spec.rb
+++ b/spec/requests/api/graphql/project/environments_spec.rb
@@ -47,6 +47,54 @@ RSpec.describe 'Project Environments query', feature_category: :continuous_deliv
expect(environment_data['environmentType']).to eq(production.environment_type)
end
+ context 'with cluster agent' do
+ let_it_be(:agent_management_project) { create(:project, :private, :repository) }
+ let_it_be(:cluster_agent) { create(:cluster_agent, project: agent_management_project) }
+
+ let_it_be(:deployment_project) { create(:project, :private, :repository) }
+ let_it_be(:environment) { create(:environment, project: deployment_project, cluster_agent: cluster_agent) }
+
+ let!(:authorization) do
+ create(:agent_user_access_project_authorization, project: deployment_project, agent: cluster_agent)
+ end
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{deployment_project.full_path}") {
+ environment(name: "#{environment.name}") {
+ clusterAgent {
+ name
+ }
+ }
+ }
+ }
+ )
+ end
+
+ before_all do
+ deployment_project.add_developer(developer)
+ end
+
+ it 'returns the cluster agent of the environment' do
+ subject
+
+ cluster_agent_data = graphql_data.dig('project', 'environment', 'clusterAgent')
+ expect(cluster_agent_data['name']).to eq(cluster_agent.name)
+ end
+
+ context 'when the cluster is not authorized in the project' do
+ let!(:authorization) { nil }
+
+ it 'does not return the cluster agent of the environment' do
+ subject
+
+ cluster_agent_data = graphql_data.dig('project', 'environment', 'clusterAgent')
+ expect(cluster_agent_data).to be_nil
+ end
+ end
+ end
+
describe 'user permissions' do
let(:query) do
%(
diff --git a/spec/requests/api/graphql/project/jira_import_spec.rb b/spec/requests/api/graphql/project/jira_import_spec.rb
index 821357b6988..25cea0238ef 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', feature_category: :integrations do
+RSpec.describe 'query Jira import data', feature_category: :importers do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index abfdf07c288..fb1489372fc 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'getting pipeline information nested in a project', feature_categ
let(:path) { %i[project pipeline] }
let(:pipeline_graphql_data) { graphql_data_at(*path) }
let(:depth) { 3 }
- let(:excluded) { %w[job project] } # Project is very expensive, due to the number of fields
+ let(:excluded) { %w[job project jobs] } # Project is very expensive, due to the number of fields
let(:fields) { all_graphql_fields_for('Pipeline', excluded: excluded, max_depth: depth) }
let(:query) do
@@ -82,7 +82,11 @@ RSpec.describe 'getting pipeline information nested in a project', feature_categ
context 'when enough data is requested' do
let(:fields) do
query_graphql_field(:jobs, nil,
- query_graphql_field(:nodes, {}, all_graphql_fields_for('CiJob', max_depth: 3)))
+ query_graphql_field(
+ :nodes, {},
+ all_graphql_fields_for('CiJob', excluded: %w[aiFailureAnalysis], max_depth: 3)
+ )
+ )
end
it 'contains jobs' do
@@ -116,7 +120,12 @@ RSpec.describe 'getting pipeline information nested in a project', feature_categ
let(:fields) do
query_graphql_field(:jobs, { retried: retried_argument },
- query_graphql_field(:nodes, {}, all_graphql_fields_for('CiJob', max_depth: 3)))
+ query_graphql_field(
+ :nodes,
+ {},
+ all_graphql_fields_for('CiJob', excluded: %w[aiFailureAnalysis], max_depth: 3)
+ )
+ )
end
context 'when we filter out retried jobs' do
@@ -177,7 +186,7 @@ RSpec.describe 'getting pipeline information nested in a project', feature_categ
pipeline(iid: $pipelineIID) {
jobs(statuses: [$status]) {
nodes {
- #{all_graphql_fields_for('CiJob', max_depth: 1)}
+ #{all_graphql_fields_for('CiJob', excluded: %w[aiFailureAnalysis], max_depth: 3)}
}
}
}
diff --git a/spec/requests/api/graphql/project/project_members_spec.rb b/spec/requests/api/graphql/project/project_members_spec.rb
index 1f1d8027592..faeb3ddd693 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', feature_category: :projects do
+RSpec.describe 'getting project members information', feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(:parent_group) { create(:group, :public) }
diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb
index 628a2117e9d..478112b687a 100644
--- a/spec/requests/api/graphql/project/work_items_spec.rb
+++ b/spec/requests/api/graphql/project/work_items_spec.rb
@@ -288,60 +288,6 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
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_all 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
-
context 'when fetching work item notifications widget' do
let(:fields) do
<<~GRAPHQL
@@ -426,26 +372,4 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
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 9f51258c163..54f141d9401 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', feature_category: :projects do
+RSpec.describe 'getting project information', feature_category: :groups_and_projects do
include GraphqlHelpers
let_it_be(:group) { create(:group) }
diff --git a/spec/requests/api/graphql/subscriptions/work_item_updated_spec.rb b/spec/requests/api/graphql/subscriptions/work_item_updated_spec.rb
new file mode 100644
index 00000000000..6c0962e7ec0
--- /dev/null
+++ b/spec/requests/api/graphql/subscriptions/work_item_updated_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Subscriptions::WorkItemUpdated, feature_category: :team_planning do
+ include GraphqlHelpers
+ include Graphql::Subscriptions::WorkItems::Helper
+
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:task) { create(:work_item, :task, project: project) }
+
+ let(:current_user) { nil }
+ let(:subscribe) { work_item_subscription('workItemUpdated', task, current_user) }
+ let(:updated_work_item) { graphql_dig_at(graphql_data(response[:result]), :workItemUpdated) }
+
+ before do
+ stub_const('GitlabSchema', Graphql::Subscriptions::ActionCable::MockGitlabSchema)
+ Graphql::Subscriptions::ActionCable::MockActionCable.clear_mocks
+ project.add_reporter(reporter)
+ end
+
+ subject(:response) do
+ subscription_response do
+ GraphqlTriggers.work_item_updated(task)
+ end
+ end
+
+ context 'when user is unauthorized' do
+ it 'does not receive any data' do
+ expect(response).to be_nil
+ end
+ end
+
+ context 'when user is authorized' do
+ let(:current_user) { reporter }
+
+ it 'receives updated work_item data' do
+ expect(updated_work_item['id']).to eq(task.to_gid.to_s)
+ expect(updated_work_item['iid']).to eq(task.iid.to_s)
+ end
+ end
+end
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 d09cb319877..d317651bd8f 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', feature_category: :subgroups do
+RSpec.describe 'GroupMember', feature_category: :groups_and_projects 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 1baa7815793..b68e9d653ad 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', feature_category: :subgroups do
+RSpec.describe 'ProjectMember', feature_category: :groups_and_projects 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 7d4284300d8..07ace6e5dca 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', feature_category: :projects do
+RSpec.describe 'Getting starredProjects of the user', feature_category: :groups_and_projects do
include GraphqlHelpers
let(:query) do
diff --git a/spec/requests/api/graphql/users/set_namespace_commit_email_spec.rb b/spec/requests/api/graphql/users/set_namespace_commit_email_spec.rb
new file mode 100644
index 00000000000..1db6f83ce4f
--- /dev/null
+++ b/spec/requests/api/graphql/users/set_namespace_commit_email_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Setting namespace commit email', feature_category: :user_profile do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:group) { create(:group, :public) }
+ let(:email) { create(:email, :confirmed, user: current_user) }
+ let(:input) { {} }
+ let(:namespace_id) { group.to_global_id }
+ let(:email_id) { email.to_global_id }
+
+ let(:resource_or_permission_error) do
+ "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ end
+
+ let(:mutation) do
+ variables = {
+ namespace_id: namespace_id,
+ email_id: email_id
+ }
+ graphql_mutation(:user_set_namespace_commit_email, variables.merge(input),
+ <<-QL.strip_heredoc
+ namespaceCommitEmail {
+ email {
+ id
+ }
+ }
+ errors
+ QL
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:user_set_namespace_commit_email)
+ end
+
+ shared_examples 'success' do
+ it 'creates a namespace commit email' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response.dig('namespaceCommitEmail', 'email', 'id')).to eq(email.to_global_id.to_s)
+ expect(graphql_errors).to be_nil
+ end
+ end
+
+ before do
+ group.add_reporter(current_user)
+ end
+
+ context 'when current_user is nil' do
+ it 'returns the top level error' do
+ post_graphql_mutation(mutation, current_user: nil)
+
+ expect(graphql_errors.first).to match a_hash_including(
+ 'message' => resource_or_permission_error)
+ end
+ end
+
+ context 'when the user cannot access the namespace' do
+ let(:namespace_id) { create(:group).to_global_id }
+
+ it 'returns the top level error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).not_to be_empty
+ expect(graphql_errors.first).to match a_hash_including(
+ 'message' => resource_or_permission_error)
+ end
+ end
+
+ context 'when the service returns an error' do
+ let(:email_id) { create(:email).to_global_id }
+
+ it 'returns the error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['errors']).to contain_exactly("Email must be provided.")
+ expect(mutation_response['namespaceCommitEmail']).to be_nil
+ end
+ end
+
+ context 'when namespace is a group' do
+ it_behaves_like 'success'
+ end
+
+ context 'when namespace is a user' do
+ let(:namespace_id) { current_user.namespace.to_global_id }
+
+ it_behaves_like 'success'
+ end
+
+ context 'when namespace is a project' do
+ let_it_be(:project) { create(:project) }
+
+ let(:namespace_id) { project.project_namespace.to_global_id }
+
+ before do
+ project.add_reporter(current_user)
+ end
+
+ it_behaves_like 'success'
+ end
+end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index dc5004a121b..6702224f303 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -69,7 +69,8 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
'deleteWorkItem' => false,
'adminWorkItem' => true,
'adminParentLink' => true,
- 'setWorkItemMetadata' => true
+ 'setWorkItemMetadata' => true,
+ 'createNote' => true
},
'project' => hash_including('id' => project.to_gid.to_s, 'fullPath' => project.full_path)
)
@@ -540,6 +541,114 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
end
end
+ describe 'notes widget' do
+ let(:work_item_fields) do
+ <<~GRAPHQL
+ id
+ widgets {
+ type
+ ... on WorkItemWidgetNotes {
+ system: discussions(filter: ONLY_ACTIVITY, first: 10) { nodes { id notes { nodes { id system internal body } } } },
+ comments: discussions(filter: ONLY_COMMENTS, first: 10) { nodes { id notes { nodes { id system internal body } } } },
+ all_notes: discussions(filter: ALL_NOTES, first: 10) { nodes { id notes { nodes { id system internal body } } } }
+ }
+ }
+ GRAPHQL
+ end
+
+ context 'when fetching award emoji from notes' do
+ let(:work_item_fields) do
+ <<~GRAPHQL
+ id
+ widgets {
+ type
+ ... on WorkItemWidgetNotes {
+ discussions(filter: ONLY_COMMENTS, first: 10) {
+ nodes {
+ id
+ notes {
+ nodes {
+ id
+ body
+ maxAccessLevelOfAuthor
+ authorIsContributor
+ awardEmoji {
+ nodes {
+ name
+ user {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ let_it_be(:note) { create(:note, project: work_item.project, noteable: work_item) }
+
+ before_all do
+ create(:award_emoji, awardable: note, name: 'rocket', user: developer)
+ end
+
+ it 'returns award emoji data' do
+ all_widgets = graphql_dig_at(work_item_data, :widgets)
+ notes_widget = all_widgets.find { |x| x['type'] == 'NOTES' }
+ notes = graphql_dig_at(notes_widget['discussions'], :nodes).flat_map { |d| d['notes']['nodes'] }
+
+ note_with_emoji = notes.find { |n| n['id'] == note.to_gid.to_s }
+
+ expect(note_with_emoji).to include(
+ 'awardEmoji' => {
+ 'nodes' => include(
+ hash_including(
+ 'name' => 'rocket',
+ 'user' => {
+ 'name' => developer.name
+ }
+ )
+ )
+ }
+ )
+ end
+
+ it 'returns author contributor status and max access level' do
+ all_widgets = graphql_dig_at(work_item_data, :widgets)
+ notes_widget = all_widgets.find { |x| x['type'] == 'NOTES' }
+ notes = graphql_dig_at(notes_widget['discussions'], :nodes).flat_map { |d| d['notes']['nodes'] }
+
+ expect(notes).to contain_exactly(
+ hash_including('maxAccessLevelOfAuthor' => 'Owner', 'authorIsContributor' => false)
+ )
+ end
+
+ it 'avoids N+1 queries' do
+ another_user = create(:user).tap { |u| note.resource_parent.add_developer(u) }
+ create(:note, project: note.project, noteable: work_item, author: another_user)
+
+ post_graphql(query, current_user: developer)
+
+ control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: developer) }
+
+ expect_graphql_errors_to_be_empty
+
+ another_note = create(:note, project: work_item.project, noteable: work_item)
+ create(:award_emoji, awardable: another_note, name: 'star', user: guest)
+ another_user = create(:user).tap { |u| note.resource_parent.add_developer(u) }
+ note_with_different_user = create(:note, project: note.project, noteable: work_item, author: another_user)
+ create(:award_emoji, awardable: note_with_different_user, name: 'star', user: developer)
+
+ # TODO: Fix existing N+1 queries in https://gitlab.com/gitlab-org/gitlab/-/issues/414747
+ expect { post_graphql(query, current_user: developer) }.not_to exceed_query_limit(control).with_threshold(3)
+ expect_graphql_errors_to_be_empty
+ end
+ end
+ end
+
context 'when an Issue Global ID is provided' do
let(:global_id) { Issue.find(work_item.id).to_gid.to_s }
diff --git a/spec/requests/api/group_avatar_spec.rb b/spec/requests/api/group_avatar_spec.rb
index 9a0e79ee9f8..c8d06aa19dc 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, feature_category: :subgroups do
+RSpec.describe API::GroupAvatar, feature_category: :groups_and_projects do
def avatar_path(group)
"/groups/#{ERB::Util.url_encode(group.full_path)}/avatar"
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 84d48b4edb4..2adf71f2a18 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, feature_category: :subgroups do
+RSpec.describe API::Groups, feature_category: :groups_and_projects do
include GroupAPIHelpers
include UploadHelpers
include WorkhorseHelpers
diff --git a/spec/requests/api/integrations_spec.rb b/spec/requests/api/integrations_spec.rb
index 8d348dc0a54..4922a07cd6c 100644
--- a/spec/requests/api/integrations_spec.rb
+++ b/spec/requests/api/integrations_spec.rb
@@ -44,11 +44,17 @@ RSpec.describe API::Integrations, feature_category: :integrations do
end
where(:integration) do
- # The API supports all integrations except the GitLab Slack Application
- # integration; this integration must be installed via the UI.
+ # The Project Integrations API supports all integrations except:
+ # - The GitLab Slack Application integration, as it must be installed via the UI.
+ # - Shimo and ZenTao integrations, as new integrations are blocked from being created.
+ unavailable_integration_names = [
+ Integrations::GitlabSlackApplication.to_param,
+ Integrations::Shimo.to_param,
+ Integrations::Zentao.to_param
+ ]
+
names = Integration.available_integration_names
- names.delete(Integrations::GitlabSlackApplication.to_param) if Gitlab.ee?
- names - %w[shimo zentao]
+ names.reject { |name| name.in?(unavailable_integration_names) }
end
with_them do
@@ -62,14 +68,13 @@ RSpec.describe API::Integrations, feature_category: :integrations do
let(:missing_attributes) do
{
datadog: %i[archive_trace_events],
- discord: %i[branches_to_be_notified notify_only_broken_pipelines],
hangouts_chat: %i[notify_only_broken_pipelines],
jira: %i[issues_enabled project_key jira_issue_regex jira_issue_prefix vulnerabilities_enabled vulnerabilities_issuetype],
- mattermost: %i[deployment_channel labels_to_be_notified],
+ mattermost: %i[labels_to_be_notified],
mock_ci: %i[enable_ssl_verification],
prometheus: %i[manual_configuration],
pumble: %i[branches_to_be_notified notify_only_broken_pipelines],
- slack: %i[alert_events alert_channel deployment_channel labels_to_be_notified],
+ slack: %i[labels_to_be_notified],
unify_circuit: %i[branches_to_be_notified notify_only_broken_pipelines],
webex_teams: %i[branches_to_be_notified notify_only_broken_pipelines]
}
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index 6414b1efe6a..619ffd8d41a 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -217,47 +217,23 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
end
end
- context 'when default_pat_expiration feature flag is true' do
- it 'returns token with expiry as PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS' do
- freeze_time do
- token_size = (PersonalAccessToken.token_prefix || '').size + 20
-
- post api('/internal/personal_access_token'),
- params: {
- key_id: key.id,
- name: 'newtoken',
- scopes: %w(read_api read_repository)
- },
- headers: gitlab_shell_internal_api_request_header
-
- expect(json_response['success']).to be_truthy
- expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
- expect(json_response['scopes']).to match_array(%w(read_api read_repository))
- expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
- end
- end
- end
-
- context 'when default_pat_expiration feature flag is false' do
- before do
- stub_feature_flags(default_pat_expiration: false)
- end
-
- it 'uses nil expiration value' do
+ it 'returns token with expiry as PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS' do
+ freeze_time do
token_size = (PersonalAccessToken.token_prefix || '').size + 20
post api('/internal/personal_access_token'),
- params: {
- key_id: key.id,
- name: 'newtoken',
- scopes: %w(read_api read_repository)
- },
- headers: gitlab_shell_internal_api_request_header
+ params: {
+ key_id: key.id,
+ name: 'newtoken',
+ scopes: %w(read_api read_repository),
+ expires_at: 365.days.from_now
+ },
+ headers: gitlab_shell_internal_api_request_header
expect(json_response['success']).to be_truthy
expect(json_response['token']).to match(/\A\S{#{token_size}}\z/)
expect(json_response['scopes']).to match_array(%w(read_api read_repository))
- expect(json_response['expires_at']).to be_nil
+ expect(json_response['expires_at']).to eq(max_pat_access_token_lifetime.iso8601)
end
end
end
@@ -513,24 +489,63 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
project.add_developer(user)
end
+ shared_context 'with env passed as a JSON' do
+ let(:obj_dir_relative) { './objects' }
+ let(:alt_obj_dirs_relative) { ['./alt-objects-1', './alt-objects-2'] }
+ let(:env) do
+ {
+ GIT_OBJECT_DIRECTORY_RELATIVE: obj_dir_relative,
+ GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: alt_obj_dirs_relative
+ }
+ end
+ end
+
shared_examples 'sets hook env' do
- context 'with env passed as a JSON' do
- let(:obj_dir_relative) { './objects' }
- let(:alt_obj_dirs_relative) { ['./alt-objects-1', './alt-objects-2'] }
- let(:env) do
- {
- GIT_OBJECT_DIRECTORY_RELATIVE: obj_dir_relative,
- GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: alt_obj_dirs_relative
- }
- end
+ include_context 'with env passed as a JSON'
- it 'sets env in RequestStore' do
- expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, env.stringify_keys)
+ it 'sets env in RequestStore' do
+ expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, env.stringify_keys)
- subject
+ subject
- expect(response).to have_gitlab_http_status(:ok)
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ shared_examples 'sets hook env and routes to primary' do
+ include_context 'with env passed as a JSON'
+
+ let(:interceptor) do
+ Class.new(::GRPC::ClientInterceptor) do
+ def route_to_primary_received?
+ @route_to_primary_count.to_i > 0
+ end
+
+ def request_response(request:, call:, method:, metadata:) # rubocop:disable Lint/UnusedMethodArgument
+ @route_to_primary_count ||= 0
+ @route_to_primary_count += 1 if metadata['gitaly-route-repository-accessor-policy'] == 'primary-only'
+
+ yield
+ end
+ end.new
+ end
+
+ before do
+ Gitlab::GitalyClient.clear_stubs!
+ allow(::Gitlab::GitalyClient).to receive(:interceptors).and_return([interceptor])
+ end
+
+ after do
+ Gitlab::GitalyClient.clear_stubs!
+ end
+
+ it 'sets env in RequestStore and routes gRPC messages to primary', :request_store do
+ expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, env.stringify_keys).and_call_original
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(interceptor.route_to_primary_received?).to be_truthy
end
end
@@ -549,6 +564,8 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(user.reload.last_activity_on).to eql(Date.today)
end
+ # Wiki repositories don't invoke any Gitaly RPCs to check for changes, so we can only test for the
+ # hook environment being set.
it_behaves_like 'sets hook env' do
let(:gl_repository) { Gitlab::GlRepository::WIKI.identifier_for_container(project.wiki) }
end
@@ -588,7 +605,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(user.reload.last_activity_on).to eql(Date.today)
end
- it_behaves_like 'sets hook env' do
+ it_behaves_like 'sets hook env and routes to primary' do
let(:gl_repository) { Gitlab::GlRepository::SNIPPET.identifier_for_container(personal_snippet) }
end
end
@@ -620,7 +637,7 @@ RSpec.describe API::Internal::Base, feature_category: :system_access do
expect(user.reload.last_activity_on).to eql(Date.today)
end
- it_behaves_like 'sets hook env' do
+ it_behaves_like 'sets hook env and routes to primary' do
let(:gl_repository) { Gitlab::GlRepository::SNIPPET.identifier_for_container(project_snippet) }
end
end
diff --git a/spec/requests/api/internal/error_tracking_spec.rb b/spec/requests/api/internal/error_tracking_spec.rb
index 83012e26138..1906bed6007 100644
--- a/spec/requests/api/internal/error_tracking_spec.rb
+++ b/spec/requests/api/internal/error_tracking_spec.rb
@@ -19,7 +19,6 @@ RSpec.describe API::Internal::ErrorTracking, feature_category: :error_tracking d
before do
# Because the feature flag is disabled in specs we have to enable it explicitly.
- stub_feature_flags(use_click_house_database_for_error_tracking: true)
stub_feature_flags(gitlab_error_tracking: true)
end
@@ -90,9 +89,8 @@ RSpec.describe API::Internal::ErrorTracking, feature_category: :error_tracking d
expect(json_response).to eq('enabled' => true)
end
- context 'when feature flags use_click_house_database_for_error_tracking or gitlab_error_tracking are disabled' do
+ context 'when feature flags gitlab_error_tracking are disabled' do
before do
- stub_feature_flags(use_click_house_database_for_error_tracking: false)
stub_feature_flags(gitlab_error_tracking: false)
end
diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb
index c07382a6e04..3c76fba4e2c 100644
--- a/spec/requests/api/internal/kubernetes_spec.rb
+++ b/spec/requests/api/internal/kubernetes_spec.rb
@@ -122,11 +122,12 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :deployment_manageme
it 'tracks events and unique events', :aggregate_failures do
request_count = 2
- counters = { gitops_sync: 10, k8s_api_proxy_request: 5 }
+ counters = { gitops_sync: 10, k8s_api_proxy_request: 5, flux_git_push_notifications_total: 42 }
unique_counters = { agent_users_using_ci_tunnel: [10, 999, 777, 10] }
expected_counters = {
kubernetes_agent_gitops_sync: request_count * counters[:gitops_sync],
- kubernetes_agent_k8s_api_proxy_request: request_count * counters[:k8s_api_proxy_request]
+ kubernetes_agent_k8s_api_proxy_request: request_count * counters[:k8s_api_proxy_request],
+ kubernetes_agent_flux_git_push_notifications_total: request_count * counters[:flux_git_push_notifications_total]
}
expected_hll_count = unique_counters[:agent_users_using_ci_tunnel].uniq.count
@@ -337,6 +338,81 @@ RSpec.describe API::Internal::Kubernetes, feature_category: :deployment_manageme
end
end
+ describe 'GET /internal/kubernetes/verify_project_access' do
+ def send_request(headers: {}, params: {})
+ get api("/internal/kubernetes/verify_project_access"), params: params, headers: headers.reverse_merge(jwt_auth_headers)
+ end
+
+ include_examples 'authorization'
+ include_examples 'agent authentication'
+ include_examples 'error handling'
+
+ shared_examples 'access is granted' do
+ it 'returns success response' do
+ send_request(params: { id: project_id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
+
+ shared_examples 'access is denied' do
+ it 'returns 404' do
+ send_request(params: { id: project_id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'an agent is found' do
+ let_it_be(:agent_token) { create(:cluster_agent_token) }
+ let(:project_id) { project.id }
+
+ include_examples 'agent token tracking'
+
+ context 'project is public' do
+ let(:project) { create(:project, :public) }
+
+ it_behaves_like 'access is granted'
+
+ context 'repository is for project members only' do
+ let(:project) { create(:project, :public, :repository_private) }
+
+ it_behaves_like 'access is denied'
+ end
+ end
+
+ context 'project is private' do
+ let(:project) { create(:project, :private) }
+
+ it_behaves_like 'access is denied'
+
+ context 'and agent belongs to project' do
+ let(:agent_token) { create(:cluster_agent_token, agent: create(:cluster_agent, project: project)) }
+
+ it_behaves_like 'access is granted'
+ end
+ end
+
+ context 'project is internal' do
+ let(:project) { create(:project, :internal) }
+
+ it_behaves_like 'access is denied'
+
+ context 'and agent belongs to project' do
+ let(:agent_token) { create(:cluster_agent_token, agent: create(:cluster_agent, project: project)) }
+
+ it_behaves_like 'access is granted'
+ end
+ end
+
+ context 'project does not exist' do
+ let(:project_id) { non_existing_record_id }
+
+ it_behaves_like 'access is denied'
+ end
+ end
+ end
+
describe 'POST /internal/kubernetes/authorize_proxy_user', :clean_gitlab_redis_sessions do
include SessionHelpers
diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb
index 5a15a0b6dad..1cd20680afb 100644
--- a/spec/requests/api/issues/post_projects_issues_spec.rb
+++ b/spec/requests/api/issues/post_projects_issues_spec.rb
@@ -416,11 +416,12 @@ RSpec.describe API::Issues, :aggregate_failures, feature_category: :team_plannin
end
before do
- expect_next_instance_of(Spam::SpamActionService) do |spam_service|
- expect(spam_service).to receive_messages(check_for_spam?: true)
+ expect_next_instance_of(Issue) do |instance|
+ expect(instance).to receive(:check_for_spam).with(user: user, action: :create).and_call_original
end
+
expect_next_instance_of(Spam::AkismetService) do |akismet_service|
- expect(akismet_service).to receive_messages(spam?: true)
+ expect(akismet_service).to receive(:spam?).and_return(true)
end
end
diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb
index db5bbd610fc..8298d0bf150 100644
--- a/spec/requests/api/markdown_spec.rb
+++ b/spec/requests/api/markdown_spec.rb
@@ -5,13 +5,18 @@ require "spec_helper"
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(:token) {} # No-op. It gets overwritten in the contexts below.
let(:disable_authenticate_markdown_api) { false }
before do
stub_commonmark_sourcepos_disabled
stub_feature_flags(authenticate_markdown_api: false) if disable_authenticate_markdown_api
- post api("/markdown", user), params: params
+ if token
+ post api("/markdown", personal_access_token: token), params: params
+ else
+ post api("/markdown", user), params: params
+ end
end
shared_examples "rendered markdown text without GFM" do
@@ -85,6 +90,13 @@ RSpec.describe API::Markdown, feature_category: :team_planning do
let(:issue_url) { "http://#{Gitlab.config.gitlab.host}/#{issue.project.namespace.path}/#{issue.project.path}/-/issues/#{issue.iid}" }
let(:text) { ":tada: Hello world! :100: #{issue.to_reference}" }
+ context "when personal access token has only read_api scope" do
+ let(:token) { create(:personal_access_token, user: user, scopes: [:read_api]) }
+ let(:params) { { text: text } }
+
+ it_behaves_like "rendered markdown text without GFM"
+ end
+
context "when not using gfm" do
context "without project" do
let(:params) { { text: text } }
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
index 60e91973b5d..4e746802500 100644
--- a/spec/requests/api/maven_packages_spec.rb
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -1020,7 +1020,7 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
upload_file(params: params.merge(job_token: job.token))
expect(response).to have_gitlab_http_status(:ok)
- expect(project.reload.packages.last.original_build_info.pipeline).to eq job.pipeline
+ expect(project.reload.packages.last.last_build_info.pipeline).to eq job.pipeline
end
it 'rejects upload without running job token' do
@@ -1155,25 +1155,6 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
expect(response).to have_gitlab_http_status(:no_content)
end
-
- context 'when the stored sha1 is not the same' do
- let(:sent_sha1) { File.read(file_upload.path) }
- let(:stored_sha1) { 'wrong sha1' }
-
- it 'logs an error and returns conflict' do
- expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
- instance_of(ArgumentError),
- message: 'maven package file sha1 conflict',
- stored_sha1: stored_sha1,
- received_sha256: Digest::SHA256.hexdigest(sent_sha1),
- sha256_hexdigest_of_stored_sha1: Digest::SHA256.hexdigest(stored_sha1)
- )
-
- upload
-
- expect(response).to have_gitlab_http_status(:conflict)
- end
- end
end
context 'for md5 file' do
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 353fddcb08d..f3e5f3ab891 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, feature_category: :subgroups do
+RSpec.describe API::Members, feature_category: :groups_and_projects 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/ml/mlflow/experiments_spec.rb b/spec/requests/api/ml/mlflow/experiments_spec.rb
index 1a2577e69e7..fc2e814752c 100644
--- a/spec/requests/api/ml/mlflow/experiments_spec.rb
+++ b/spec/requests/api/ml/mlflow/experiments_spec.rb
@@ -20,7 +20,6 @@ RSpec.describe API::Ml::Mlflow::Experiments, feature_category: :mlops do
end
let(:current_user) { developer }
- let(:ff_value) { true }
let(:access_token) { tokens[:write] }
let(:headers) { { 'Authorization' => "Bearer #{access_token.token}" } }
let(:project_id) { project.id }
@@ -52,10 +51,6 @@ RSpec.describe API::Ml::Mlflow::Experiments, feature_category: :mlops do
response
end
- before do
- stub_feature_flags(ml_experiment_tracking: ff_value)
- end
-
describe 'GET /projects/:id/ml/mlflow/api/2.0/mlflow/experiments/get' do
let(:experiment_iid) { experiment.iid.to_s }
let(:route) { "/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/experiments/get?experiment_id=#{experiment_iid}" }
diff --git a/spec/requests/api/ml/mlflow/runs_spec.rb b/spec/requests/api/ml/mlflow/runs_spec.rb
index 746372b7978..a85fe4d867a 100644
--- a/spec/requests/api/ml/mlflow/runs_spec.rb
+++ b/spec/requests/api/ml/mlflow/runs_spec.rb
@@ -26,7 +26,6 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do
end
let(:current_user) { developer }
- let(:ff_value) { true }
let(:access_token) { tokens[:write] }
let(:headers) { { 'Authorization' => "Bearer #{access_token.token}" } }
let(:project_id) { project.id }
@@ -40,10 +39,6 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do
response
end
- before do
- stub_feature_flags(ml_experiment_tracking: ff_value)
- end
-
RSpec.shared_examples 'MLflow|run_id param error cases' do
context 'when run id is not passed' do
let(:params) { {} }
diff --git a/spec/requests/api/ml_model_packages_spec.rb b/spec/requests/api/ml_model_packages_spec.rb
new file mode 100644
index 00000000000..9c19f522e46
--- /dev/null
+++ b/spec/requests/api/ml_model_packages_spec.rb
@@ -0,0 +1,200 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::API::MlModelPackages, feature_category: :mlops do
+ include HttpBasicAuthHelpers
+ include PackagesManagerApiSpecHelpers
+ include WorkhorseHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ include_context 'workhorse headers'
+
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:personal_access_token) { create(:personal_access_token) }
+ let_it_be(:job) { create(:ci_build, :running, user: personal_access_token.user, project: project) }
+ 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(:another_project, reload: true) { create(:project) }
+
+ let_it_be(:tokens) do
+ {
+ personal_access_token: personal_access_token.token,
+ deploy_token: deploy_token.token,
+ job_token: job.token
+ }
+ end
+
+ let(:user) { personal_access_token.user }
+ let(:user_role) { :developer }
+ let(:member) { true }
+ let(:ci_build) { create(:ci_build, :running, user: user, project: project) }
+ let(:project_to_enable_ff) { project }
+ let(:headers) { {} }
+
+ shared_context 'ml model authorize permissions table' do # rubocop:disable RSpec/ContextWording
+ # rubocop:disable Metrics/AbcSize
+ # :visibility, :user_role, :member, :token_type, :valid_token, :expected_status
+ def authorize_permissions_table
+ :public | :developer | true | :personal_access_token | true | :success
+ :public | :guest | true | :personal_access_token | true | :forbidden
+ :public | :developer | true | :personal_access_token | false | :unauthorized
+ :public | :guest | true | :personal_access_token | false | :unauthorized
+ :public | :developer | false | :personal_access_token | true | :forbidden
+ :public | :guest | false | :personal_access_token | true | :forbidden
+ :public | :developer | false | :personal_access_token | false | :unauthorized
+ :public | :guest | false | :personal_access_token | false | :unauthorized
+ :public | :anonymous | false | :personal_access_token | true | :unauthorized
+ :private | :developer | true | :personal_access_token | true | :success
+ :private | :guest | true | :personal_access_token | true | :forbidden
+ :private | :developer | true | :personal_access_token | false | :unauthorized
+ :private | :guest | true | :personal_access_token | false | :unauthorized
+ :private | :developer | false | :personal_access_token | true | :not_found
+ :private | :guest | false | :personal_access_token | true | :not_found
+ :private | :developer | false | :personal_access_token | false | :unauthorized
+ :private | :guest | false | :personal_access_token | false | :unauthorized
+ :private | :anonymous | false | :personal_access_token | true | :unauthorized
+ :public | :developer | true | :job_token | true | :success
+ :public | :guest | true | :job_token | true | :forbidden
+ :public | :developer | true | :job_token | false | :unauthorized
+ :public | :guest | true | :job_token | false | :unauthorized
+ :public | :developer | false | :job_token | true | :forbidden
+ :public | :guest | false | :job_token | true | :forbidden
+ :public | :developer | false | :job_token | false | :unauthorized
+ :public | :guest | false | :job_token | false | :unauthorized
+ :private | :developer | true | :job_token | true | :success
+ :private | :guest | true | :job_token | true | :forbidden
+ :private | :developer | true | :job_token | false | :unauthorized
+ :private | :guest | true | :job_token | false | :unauthorized
+ :private | :developer | false | :job_token | true | :not_found
+ :private | :guest | false | :job_token | true | :not_found
+ :private | :developer | false | :job_token | false | :unauthorized
+ :private | :guest | false | :job_token | false | :unauthorized
+ :public | :developer | true | :deploy_token | true | :success
+ :public | :developer | true | :deploy_token | false | :unauthorized
+ :private | :developer | true | :deploy_token | true | :success
+ :private | :developer | true | :deploy_token | false | :unauthorized
+ end
+ # rubocop:enable Metrics/AbcSize
+ end
+
+ before do
+ project.send("add_#{user_role}", user) if member && user_role != :anonymous
+ end
+
+ subject(:api_response) do
+ request
+ response
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/ml_models/:package_name/:package_version/:file_name/authorize' do
+ include_context 'ml model authorize permissions table'
+
+ let(:token) { tokens[:personal_access_token] }
+ let(:user_headers) { { 'HTTP_AUTHORIZATION' => token } }
+ let(:headers) { user_headers.merge(workhorse_headers) }
+ let(:request) { authorize_upload_file(headers) }
+
+ describe 'user access' do
+ where(:visibility, :user_role, :member, :token_type, :valid_token, :expected_status) do
+ authorize_permissions_table
+ end
+
+ with_them do
+ let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
+ let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } }
+
+ before do
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s))
+ end
+
+ it { is_expected.to have_gitlab_http_status(expected_status) }
+ end
+
+ it_behaves_like 'Endpoint not found if read_model_registry not available'
+ end
+
+ describe 'application security' do
+ where(:param_name, :param_value) do
+ :package_name | 'my-package/../'
+ :package_name | 'my-package%2f%2e%2e%2f'
+ :file_name | '../.ssh%2fauthorized_keys'
+ :file_name | '%2e%2e%2f.ssh%2fauthorized_keys'
+ end
+
+ with_them do
+ let(:request) { authorize_upload_file(headers, param_name => param_value) }
+
+ it 'rejects malicious request' do
+ is_expected.to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/ml_models/:package_name/:package_version/:file_name' do
+ include_context 'ml model authorize permissions table'
+
+ let_it_be(:file_name) { 'model.md5' }
+
+ let(:token) { tokens[:personal_access_token] }
+ let(:user_headers) { { 'HTTP_AUTHORIZATION' => token } }
+ let(:headers) { user_headers.merge(workhorse_headers) }
+ let(:params) { { file: temp_file(file_name) } }
+ let(:file_key) { :file }
+ let(:send_rewritten_field) { true }
+
+ let(:request) do
+ upload_file(headers)
+ end
+
+ describe 'success' do
+ it 'creates a new package' do
+ expect { api_response }.to change { Packages::PackageFile.count }.by(1)
+ expect(api_response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ describe 'user access' do
+ where(:visibility, :user_role, :member, :token_type, :valid_token, :expected_status) do
+ authorize_permissions_table
+ end
+
+ with_them do
+ let(:token) { valid_token ? tokens[token_type] : 'invalid-token123' }
+ let(:user_headers) { user_role == :anonymous ? {} : { 'HTTP_AUTHORIZATION' => token } }
+
+ before do
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility.to_s))
+ end
+
+ if params[:expected_status] == :success
+ it_behaves_like 'process ml model package upload'
+ else
+ it { is_expected.to have_gitlab_http_status(expected_status) }
+ end
+ end
+
+ it_behaves_like 'Endpoint not found if read_model_registry not available'
+ end
+ end
+
+ def authorize_upload_file(request_headers, package_name: 'mypackage', file_name: 'myfile.tar.gz')
+ url = "/projects/#{project.id}/packages/ml_models/#{package_name}/0.0.1/#{file_name}/authorize"
+
+ put api(url), headers: request_headers
+ end
+
+ def upload_file(request_headers, package_name: 'mypackage')
+ url = "/projects/#{project.id}/packages/ml_models/#{package_name}/0.0.1/#{file_name}"
+
+ workhorse_finalize(
+ api(url),
+ method: :put,
+ file_key: file_key,
+ params: params,
+ headers: request_headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+end
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index f268a092034..f796edfb20e 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, :aggregate_failures, feature_category: :subgroups do
+RSpec.describe API::Namespaces, :aggregate_failures, feature_category: :groups_and_projects do
let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) }
let_it_be(:group1) { create(:group, name: 'group.one') }
@@ -30,7 +30,7 @@ RSpec.describe API::Namespaces, :aggregate_failures, feature_category: :subgroup
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(group_kind_json_response.keys).to include('id', 'kind', 'name', 'path', 'full_path',
- 'parent_id', 'members_count_with_descendants')
+ 'parent_id', 'members_count_with_descendants', 'root_repository_size')
expect(user_kind_json_response.keys).to include('id', 'kind', 'name', 'path', 'full_path', 'parent_id')
end
@@ -66,7 +66,7 @@ RSpec.describe API::Namespaces, :aggregate_failures, feature_category: :subgroup
owned_group_response = json_response.find { |resource| resource['id'] == group1.id }
expect(owned_group_response.keys).to include('id', 'kind', 'name', 'path', 'full_path',
- 'parent_id', 'members_count_with_descendants')
+ 'parent_id', 'members_count_with_descendants', 'root_repository_size')
end
it "returns correct attributes when user cannot admin group" do
diff --git a/spec/requests/api/npm_group_packages_spec.rb b/spec/requests/api/npm_group_packages_spec.rb
new file mode 100644
index 00000000000..d97c7682b4b
--- /dev/null
+++ b/spec/requests/api/npm_group_packages_spec.rb
@@ -0,0 +1,186 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::NpmGroupPackages, feature_category: :package_registry do
+ using RSpec::Parameterized::TableSyntax
+
+ include_context 'npm api setup'
+
+ describe 'GET /api/v4/groups/:id/-/packages/npm/*package_name' do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/#{package_name}") }
+
+ it_behaves_like 'handling get metadata requests', scope: :group
+
+ context 'with a duplicate package name in another project' do
+ subject { get(url) }
+
+ before do
+ group.add_developer(user)
+ end
+
+ let_it_be(:project2) { create(:project, :public, namespace: namespace) }
+ let_it_be(:package2) do
+ create(:npm_package,
+ project: project2,
+ name: "@#{group.path}/scoped_package",
+ version: '1.2.0')
+ end
+
+ it_behaves_like 'rejects invalid package names'
+
+ it 'includes all matching package versions in the response' do
+ subject
+
+ expect(json_response['versions'].keys).to match_array([package.version, package2.version])
+ end
+
+ context 'with the feature flag disabled' do
+ before do
+ stub_feature_flags(npm_allow_packages_in_multiple_projects: false)
+ end
+
+ it 'returns matching package versions from only one project' do
+ subject
+
+ expect(json_response['versions'].keys).to match_array([package2.version])
+ end
+ end
+ end
+
+ context 'with mixed group and project visibilities' do
+ subject { get(url, headers: headers) }
+
+ where(:auth, :group_visibility, :project_visibility, :user_role, :expected_status) do
+ nil | :public | :public | nil | :ok
+ nil | :public | :internal | nil | :not_found
+ nil | :public | :private | nil | :not_found
+ nil | :internal | :internal | nil | :not_found
+ nil | :internal | :private | nil | :not_found
+ nil | :private | :private | nil | :not_found
+
+ :oauth | :public | :public | :guest | :ok
+ :oauth | :public | :internal | :guest | :ok
+ :oauth | :public | :private | :guest | :forbidden
+ :oauth | :internal | :internal | :guest | :ok
+ :oauth | :internal | :private | :guest | :forbidden
+ :oauth | :private | :private | :guest | :forbidden
+ :oauth | :public | :public | :reporter | :ok
+ :oauth | :public | :internal | :reporter | :ok
+ :oauth | :public | :private | :reporter | :ok
+ :oauth | :internal | :internal | :reporter | :ok
+ :oauth | :internal | :private | :reporter | :ok
+ :oauth | :private | :private | :reporter | :ok
+
+ :personal_access_token | :public | :public | :guest | :ok
+ :personal_access_token | :public | :internal | :guest | :ok
+ :personal_access_token | :public | :private | :guest | :forbidden
+ :personal_access_token | :internal | :internal | :guest | :ok
+ :personal_access_token | :internal | :private | :guest | :forbidden
+ :personal_access_token | :private | :private | :guest | :forbidden
+ :personal_access_token | :public | :public | :reporter | :ok
+ :personal_access_token | :public | :internal | :reporter | :ok
+ :personal_access_token | :public | :private | :reporter | :ok
+ :personal_access_token | :internal | :internal | :reporter | :ok
+ :personal_access_token | :internal | :private | :reporter | :ok
+ :personal_access_token | :private | :private | :reporter | :ok
+
+ :job_token | :public | :public | :developer | :ok
+ :job_token | :public | :internal | :developer | :ok
+ :job_token | :public | :private | :developer | :ok
+ :job_token | :internal | :internal | :developer | :ok
+ :job_token | :internal | :private | :developer | :ok
+ :job_token | :private | :private | :developer | :ok
+
+ :deploy_token | :public | :public | nil | :ok
+ :deploy_token | :public | :internal | nil | :ok
+ :deploy_token | :public | :private | nil | :ok
+ :deploy_token | :internal | :internal | nil | :ok
+ :deploy_token | :internal | :private | nil | :ok
+ :deploy_token | :private | :private | nil | :ok
+ end
+
+ with_them do
+ let(:headers) do
+ case auth
+ when :oauth
+ build_token_auth_header(token.plaintext_token)
+ when :personal_access_token
+ build_token_auth_header(personal_access_token.token)
+ when :job_token
+ build_token_auth_header(job.token)
+ when :deploy_token
+ build_token_auth_header(deploy_token.token)
+ else
+ {}
+ end
+ end
+
+ before do
+ project.update!(visibility: project_visibility.to_s)
+ project.send("add_#{user_role}", user) if user_role
+ group.update!(visibility: group_visibility.to_s)
+ group.send("add_#{user_role}", user) if user_role
+ end
+
+ it_behaves_like 'returning response status', params[:expected_status]
+ end
+ end
+
+ context 'when user is a reporter of project but is not a direct member of group' do
+ subject { get(url, headers: headers) }
+
+ where(:group_visibility, :project_visibility, :expected_status) do
+ :public | :public | :ok
+ :public | :internal | :ok
+ :public | :private | :ok
+ :internal | :internal | :ok
+ :internal | :private | :ok
+ :private | :private | :ok
+ end
+
+ with_them do
+ let(:headers) { build_token_auth_header(personal_access_token.token) }
+
+ before do
+ project.update!(visibility: project_visibility.to_s)
+ project.add_reporter(user)
+
+ group.update!(visibility: group_visibility.to_s)
+ end
+
+ it_behaves_like 'returning response status', params[:expected_status]
+ end
+ end
+ end
+
+ describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do
+ it_behaves_like 'handling get dist tags requests', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags") }
+ end
+ end
+
+ describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ it_behaves_like 'handling create dist tag requests', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
+ end
+ end
+
+ describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
+ it_behaves_like 'handling delete dist tag requests', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
+ end
+ end
+
+ describe 'POST /api/v4/groups/:id/-/packages/npm/-/npm/v1/security/advisories/bulk' do
+ it_behaves_like 'handling audit request', path: 'advisories/bulk', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/-/npm/v1/security/advisories/bulk") }
+ end
+ end
+
+ describe 'POST /api/v4/groups/:id/-/packages/npm/-/npm/v1/security/audits/quick' do
+ it_behaves_like 'handling audit request', path: 'audits/quick', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/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 d673645c51a..60d4bddc502 100644
--- a/spec/requests/api/npm_project_packages_spec.rb
+++ b/spec/requests/api/npm_project_packages_spec.rb
@@ -265,7 +265,7 @@ RSpec.describe API::NpmProjectPackages, feature_category: :package_registry do
upload_package_with_token
expect(response).to have_gitlab_http_status(:ok)
- expect(project.reload.packages.find(json_response['id']).original_build_info.pipeline).to eq job.pipeline
+ expect(project.reload.packages.find(json_response['id']).last_build_info.pipeline).to eq job.pipeline
end
end
end
diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb
index facbc01220d..07199119cb5 100644
--- a/spec/requests/api/nuget_group_packages_spec.rb
+++ b/spec/requests/api/nuget_group_packages_spec.rb
@@ -26,13 +26,7 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do
shared_examples 'handling all endpoints' do
describe 'GET /api/v4/groups/:id/-/packages/nuget' do
- it_behaves_like 'handling nuget service requests',
- example_names_with_status: {
- anonymous_requests_example_name: 'rejects nuget packages access',
- anonymous_requests_status: :unauthorized,
- guest_requests_example_name: 'process nuget service index request',
- guest_requests_status: :success
- } do
+ it_behaves_like 'handling nuget service requests' do
let(:url) { "/groups/#{target.id}/-/packages/nuget/index.json" }
end
end
diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb
index 9ca027c2edc..42d83ff8139 100644
--- a/spec/requests/api/pages_domains_spec.rb
+++ b/spec/requests/api/pages_domains_spec.rb
@@ -440,8 +440,8 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/pages_domain/detail')
expect(pages_domain_with_letsencrypt.auto_ssl_enabled).to be false
- expect(pages_domain_with_letsencrypt.key).to be
- expect(pages_domain_with_letsencrypt.certificate).to be
+ expect(pages_domain_with_letsencrypt.key).to be_present
+ expect(pages_domain_with_letsencrypt.certificate).to be_present
end
it 'updates pages domain with expired certificate', :aggregate_failures do
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index e9581265bb0..e0e9c944fe4 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -127,6 +127,7 @@ project_feature:
- project_id
- updated_at
- operations_access_level
+ - model_experiments_access_level
computed_attributes:
- issues_enabled
- jobs_enabled
@@ -180,6 +181,9 @@ project_setting:
- cube_api_key
- encrypted_cube_api_key
- encrypted_cube_api_key_iv
+ - encrypted_product_analytics_configurator_connection_string
+ - encrypted_product_analytics_configurator_connection_string_iv
+ - product_analytics_configurator_connection_string
build_service_desk_setting: # service_desk_setting
unexposed_attributes:
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 22d7ea36f6c..434936c0ee7 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -284,7 +284,7 @@ RSpec.describe API::ProjectExport, :aggregate_failures, :clean_gitlab_redis_cach
stub_application_setting(project_download_export_limit: 1)
end
- it 'throttles downloads within same namespaces' do
+ it 'throttles downloads within same namespaces', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/413230' do
# simulate prior request to the same namespace, which increments the rate limit counter for that scope
Gitlab::ApplicationRateLimiter.throttled?(:project_download_export, scope: [user, project_finished.namespace])
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 8e5e9d847ea..c6bf77e5dcf 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', feature_category: :integrations do
+RSpec.describe API::ProjectHooks, 'ProjectHooks', feature_category: :webhooks 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_job_token_scope_spec.rb b/spec/requests/api/project_job_token_scope_spec.rb
index df210a00012..06e28d57ca6 100644
--- a/spec/requests/api/project_job_token_scope_spec.rb
+++ b/spec/requests/api/project_job_token_scope_spec.rb
@@ -73,4 +73,444 @@ RSpec.describe API::ProjectJobTokenScope, feature_category: :secrets_management
end
end
end
+
+ describe 'PATCH /projects/:id/job_token_scope' do
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:user) { create(:user) }
+
+ let(:patch_job_token_scope_path) { "/projects/#{project.id}/job_token_scope" }
+ let(:patch_job_token_scope_params) do
+ { enabled: false }
+ end
+
+ subject { patch api(patch_job_token_scope_path, user), params: patch_job_token_scope_params }
+
+ context 'when unauthenticated user (missing user)' do
+ context 'for public project' do
+ it 'does not return ci cd settings of job token' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ patch api(patch_job_token_scope_path), params: patch_job_token_scope_params
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context 'when authenticated user as maintainer' do
+ before_all { project.add_maintainer(user) }
+
+ it 'returns unauthorized and blank response when invalid auth credentials are given' do
+ invalid_personal_access_token = build(:personal_access_token, user: user)
+
+ patch api(patch_job_token_scope_path, user, personal_access_token: invalid_personal_access_token),
+ params: patch_job_token_scope_params
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'returns no content and updates the ci cd setting `ci_inbound_job_token_scope_enabled`' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(response.body).to be_blank
+
+ project.reload
+
+ expect(project.reload.ci_inbound_job_token_scope_enabled?).to be_falsey
+ expect(project.reload.ci_outbound_job_token_scope_enabled?).to be_falsey
+ end
+
+ it 'returns bad_request when ::Projects::UpdateService fails' do
+ project_update_service_result = { status: :error, message: "any_internal_error_message" }
+ project_update_service = instance_double(Projects::UpdateService, execute: project_update_service_result)
+ allow(::Projects::UpdateService).to receive(:new).and_return(project_update_service)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response).to be_present
+ end
+
+ it 'returns bad_request when invalid value for parameter is given' do
+ patch api(patch_job_token_scope_path, user), params: {}
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns bad_request when invalid parameter given, e.g. truthy value' do
+ patch api(patch_job_token_scope_path, user), params: { enabled: 123 }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns bad_request when invalid parameter given, e.g. `nil`' do
+ patch api(patch_job_token_scope_path, user), params: { enabled: nil }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns bad_request and leaves it untouched when unpermitted parameter given' do
+ expect do
+ patch api(patch_job_token_scope_path, user),
+ params: {
+ irrelevant_parameter_boolean: true,
+ irrelevant_parameter_number: 12.34
+ }
+ end.not_to change { project.reload.updated_at }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+
+ project_reloaded = Project.find(project.id)
+ expect(project_reloaded.ci_inbound_job_token_scope_enabled?).to eq project.ci_inbound_job_token_scope_enabled?
+ expect(project_reloaded.ci_outbound_job_token_scope_enabled?).to eq project.ci_outbound_job_token_scope_enabled?
+ end
+
+ # We intend to deprecate the possibility to enable the outbound job token scope until gitlab release `v17.0` .
+ it 'returns bad_request when param `outbound_scope_enabled` given' do
+ patch api(patch_job_token_scope_path, user), params: { outbound_scope_enabled: true }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+
+ project.reload
+
+ expect(project.reload.ci_inbound_job_token_scope_enabled?).to be_truthy
+ expect(project.reload.ci_outbound_job_token_scope_enabled?).to be_falsey
+ end
+ end
+
+ context 'when authenticated user as developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns forbidden and no ci cd settings for public project' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe "GET /projects/:id/job_token_scope/allowlist" do
+ let_it_be(:project) { create(:project, :public) }
+
+ let_it_be(:user) { create(:user) }
+
+ let(:get_job_token_scope_allowlist_path) { "/projects/#{project.id}/job_token_scope/allowlist" }
+
+ subject { get api(get_job_token_scope_allowlist_path, user) }
+
+ context 'when unauthenticated user (missing user)' do
+ context 'for public project' do
+ it 'does not return ci cd settings of job token' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ get api(get_job_token_scope_allowlist_path)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context 'when authenticated user as maintainer' do
+ before_all { project.add_maintainer(user) }
+
+ it 'returns allowlist containing only the source projects' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_present
+ expect(json_response).to include hash_including("id" => project.id)
+ end
+
+ it 'returns allowlist of project' do
+ create(:ci_job_token_project_scope_link, source_project: project, direction: :inbound)
+ create(:ci_job_token_project_scope_link, source_project: project, direction: :outbound)
+
+ ci_job_token_project_scope_link =
+ create(
+ :ci_job_token_project_scope_link,
+ source_project: project,
+ direction: :inbound
+ )
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq 3
+ expect(json_response).to include(
+ hash_including("id" => project.id),
+ hash_including("id" => ci_job_token_project_scope_link.target_project.id)
+ )
+ end
+
+ context 'when authenticated user as developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns forbidden and no ci cd settings for public project' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+ end
+
+ describe "POST /projects/:id/job_token_scope/allowlist" do
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:project_inbound_allowed) { create(:project, :public) }
+ let_it_be(:user) { create(:user) }
+
+ let(:post_job_token_scope_allowlist_path) { "/projects/#{project.id}/job_token_scope/allowlist" }
+
+ let(:post_job_token_scope_allowlist_params) do
+ { target_project_id: project_inbound_allowed.id }
+ end
+
+ subject do
+ post api(post_job_token_scope_allowlist_path, user), params: post_job_token_scope_allowlist_params
+ end
+
+ context 'when unauthenticated user (missing user)' do
+ context 'for public project' do
+ it 'does not return ci cd settings of job token' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ post api(post_job_token_scope_allowlist_path)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context 'when authenticated user as maintainer' do
+ before_all { project.add_maintainer(user) }
+
+ it 'returns unauthorized and blank response when invalid auth credentials are given' do
+ invalid_personal_access_token = build(:personal_access_token, user: user)
+
+ post api(post_job_token_scope_allowlist_path, user, personal_access_token: invalid_personal_access_token),
+ params: post_job_token_scope_allowlist_params
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'returns created and creates job token scope link' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to be_present
+ expect(json_response).to include(
+ "target_project_id" => project_inbound_allowed.id,
+ "source_project_id" => project.id
+ )
+ expect(json_response).not_to include "id", "direction"
+ end
+
+ it 'returns bad_request and does not create an additional job token scope link' do
+ create(
+ :ci_job_token_project_scope_link,
+ source_project: project,
+ target_project: project_inbound_allowed,
+ direction: :inbound
+ )
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns bad_request when adding the source project' do
+ post api(post_job_token_scope_allowlist_path, user), params: { target_project_id: project.id }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns not_found when project for param `project_id` does not exist' do
+ post api(post_job_token_scope_allowlist_path, user), params: { target_project_id: non_existing_record_id }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns :bad_request when parameter `project_id` missing' do
+ post api(post_job_token_scope_allowlist_path, user), params: {}
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns :bad_request when parameter `project_id` is nil value' do
+ post api(post_job_token_scope_allowlist_path, user), params: { target_project_id: nil }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns :bad_request when parameter `project_id` is empty value' do
+ post api(post_job_token_scope_allowlist_path, user), params: { target_project_id: '' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'returns :bad_request when parameter `project_id` is float value' do
+ post api(post_job_token_scope_allowlist_path, user), params: { target_project_id: 12.34 }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when authenticated user as developer' do
+ before_all { project.add_developer(user) }
+
+ context 'for private project' do
+ it 'returns forbidden and no ci cd settings' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'for public project' do
+ it 'returns forbidden and no ci cd settings' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/job_token_scope/allowlist/:target_project_id' do
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:target_project) { create(:project, :public) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:link) do
+ create(:ci_job_token_project_scope_link,
+ source_project: project,
+ target_project: target_project)
+ end
+
+ let(:project_id) { project.id }
+ let(:delete_job_token_scope_path) do
+ "/projects/#{project_id}/job_token_scope/allowlist/#{target_project.id}"
+ end
+
+ subject { delete api(delete_job_token_scope_path, user) }
+
+ context 'when unauthenticated user (missing user)' do
+ let(:user) { nil }
+
+ context 'for public project' do
+ it 'does not delete requested project from allowlist' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ context 'when user has no permissions to project' do
+ it 'responds with 401 forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when authenticated user as a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns 403 Forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when authenticated user as a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'for the target project member' do
+ before do
+ target_project.add_guest(user)
+ end
+
+ it 'returns no content and deletes requested project from allowlist' do
+ expect_next_instance_of(
+ Ci::JobTokenScope::RemoveProjectService,
+ project,
+ user
+ ) do |service|
+ expect(service).to receive(:execute).with(target_project, :inbound)
+ .and_return(instance_double('ServiceResponse', success?: true))
+ end
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(response.body).to be_blank
+ end
+
+ context 'when fails to remove target project' do
+ it 'returns a bad request' do
+ expect_next_instance_of(
+ Ci::JobTokenScope::RemoveProjectService,
+ project,
+ user
+ ) do |service|
+ expect(service).to receive(:execute).with(target_project, :inbound)
+ .and_return(instance_double('ServiceResponse',
+ success?: false,
+ reason: nil,
+ message: 'Failed to remove'))
+ end
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
+ context 'when user project does not exists' do
+ before do
+ project.destroy!
+ end
+
+ it 'responds with 404 Not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when target project does not exists' do
+ before do
+ target_project.destroy!
+ end
+
+ it 'responds with 404 Not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/project_packages_spec.rb b/spec/requests/api/project_packages_spec.rb
index c003ae9cd48..b84b7e9c52d 100644
--- a/spec/requests/api/project_packages_spec.rb
+++ b/spec/requests/api/project_packages_spec.rb
@@ -3,9 +3,11 @@
require 'spec_helper'
RSpec.describe API::ProjectPackages, feature_category: :package_registry do
- let_it_be(:project) { create(:project, :public) }
+ using RSpec::Parameterized::TableSyntax
- let(:user) { create(:user) }
+ let_it_be_with_reload(:project) { create(:project, :public) }
+
+ let_it_be(:user) { create(:user) }
let!(:package1) { create(:npm_package, :last_downloaded_at, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
let(:package_url) { "/projects/#{project.id}/packages/#{package1.id}" }
let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
@@ -101,7 +103,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
end
context 'project is private' do
- let(:project) { create(:project, :private) }
+ let_it_be(:project) { create(:project, :private) }
context 'for unauthenticated user' do
it_behaves_like 'rejects packages access', :project, :no_type, :not_found
@@ -235,7 +237,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
expect do
get api(package_url, user)
- end.not_to exceed_query_limit(control)
+ end.not_to exceed_query_limit(control).with_threshold(4)
end
end
@@ -286,7 +288,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
end
context 'project is private' do
- let(:project) { create(:project, :private) }
+ let_it_be(:project) { create(:project, :private) }
it 'returns 404 for non authenticated user' do
get api(package_url)
@@ -362,6 +364,235 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
end
end
+ describe 'GET /projects/:id/packages/:package_id/pipelines' do
+ let(:package_pipelines_url) { "/projects/#{project.id}/packages/#{package1.id}/pipelines" }
+
+ let(:tokens) do
+ {
+ personal_access_token: personal_access_token.token,
+ job_token: job.token
+ }
+ end
+
+ let_it_be(:personal_access_token) { create(:personal_access_token) }
+ let_it_be(:user) { personal_access_token.user }
+ let_it_be(:job) { create(:ci_build, :running, user: user, project: project) }
+ let(:headers) { {} }
+
+ subject { get api(package_pipelines_url) }
+
+ shared_examples 'returns package pipelines' do |expected_status|
+ it 'returns the first page of package pipelines' do
+ subject
+
+ expect(response).to have_gitlab_http_status(expected_status)
+ expect(response).to match_response_schema('public_api/v4/packages/pipelines')
+ expect(json_response.length).to eq(3)
+ expect(json_response.pluck('id')).to eq(pipelines.reverse.map(&:id))
+ end
+ end
+
+ context 'without the need for a license' do
+ context 'when the package does not exist' do
+ let(:package_pipelines_url) { "/projects/#{project.id}/packages/0/pipelines" }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'when there are no pipelines for the package' do
+ let(:package_pipelines_url) { "/projects/#{project.id}/packages/#{package2.id}/pipelines" }
+
+ it 'returns an empty response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(response).to match_response_schema('public_api/v4/packages/pipelines')
+ expect(json_response.length).to eq(0)
+ end
+ end
+
+ context 'with valid package and pipelines' do
+ let!(:pipelines) do
+ create_list(:ci_pipeline, 3, user: user, project: project).each do |pipeline|
+ create(:package_build_info, package: package1, pipeline: pipeline)
+ end
+ end
+
+ where(:visibility, :user_role, :member, :token_type, :valid_token, :shared_examples_name, :expected_status) do
+ :public | :developer | true | :personal_access_token | true | 'returns package pipelines' | :success
+ :public | :guest | true | :personal_access_token | true | 'returns package pipelines' | :success
+ :public | :developer | true | :personal_access_token | false | 'returning response status' | :unauthorized
+ :public | :guest | true | :personal_access_token | false | 'returning response status' | :unauthorized
+ :public | :developer | false | :personal_access_token | true | 'returns package pipelines' | :success
+ :public | :guest | false | :personal_access_token | true | 'returns package pipelines' | :success
+ :public | :developer | false | :personal_access_token | false | 'returning response status' | :unauthorized
+ :public | :guest | false | :personal_access_token | false | 'returning response status' | :unauthorized
+ :public | :anonymous | false | nil | true | 'returns package pipelines' | :success
+ :private | :developer | true | :personal_access_token | true | 'returns package pipelines' | :success
+ :private | :guest | true | :personal_access_token | true | 'returning response status' | :forbidden
+ :private | :developer | true | :personal_access_token | false | 'returning response status' | :unauthorized
+ :private | :guest | true | :personal_access_token | false | 'returning response status' | :unauthorized
+ :private | :developer | false | :personal_access_token | true | 'returning response status' | :not_found
+ :private | :guest | false | :personal_access_token | true | 'returning response status' | :not_found
+ :private | :developer | false | :personal_access_token | false | 'returning response status' | :unauthorized
+ :private | :guest | false | :personal_access_token | false | 'returning response status' | :unauthorized
+ :private | :anonymous | false | nil | true | 'returning response status' | :not_found
+ :public | :developer | true | :job_token | true | 'returns package pipelines' | :success
+ :public | :guest | true | :job_token | true | 'returns package pipelines' | :success
+ :public | :developer | true | :job_token | false | 'returning response status' | :unauthorized
+ :public | :guest | true | :job_token | false | 'returning response status' | :unauthorized
+ :public | :developer | false | :job_token | true | 'returns package pipelines' | :success
+ :public | :guest | false | :job_token | true | 'returns package pipelines' | :success
+ :public | :developer | false | :job_token | false | 'returning response status' | :unauthorized
+ :public | :guest | false | :job_token | false | 'returning response status' | :unauthorized
+ :private | :developer | true | :job_token | true | 'returns package pipelines' | :success
+ # TODO uncomment the spec below when https://gitlab.com/gitlab-org/gitlab/-/issues/370998 is resolved
+ # :private | :guest | true | :job_token | true | 'returning response status' | :forbidden
+ :private | :developer | true | :job_token | false | 'returning response status' | :unauthorized
+ :private | :guest | true | :job_token | false | 'returning response status' | :unauthorized
+ :private | :developer | false | :job_token | true | 'returning response status' | :not_found
+ :private | :guest | false | :job_token | true | 'returning response status' | :not_found
+ :private | :developer | false | :job_token | false | 'returning response status' | :unauthorized
+ :private | :guest | false | :job_token | false | 'returning response status' | :unauthorized
+ end
+
+ with_them do
+ subject { get api(package_pipelines_url), headers: headers }
+
+ let(:invalid_token) { 'invalid-token123' }
+ let(:token) { valid_token ? tokens[token_type] : invalid_token }
+ let(:headers) do
+ case token_type
+ when :personal_access_token
+ { Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER => token }
+ when :job_token
+ { Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER => token }
+ when nil
+ {}
+ end
+ end
+
+ before do
+ project.update!(visibility: visibility.to_s)
+ project.send("add_#{user_role}", user) if member && user_role != :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name], params[:expected_status]
+ end
+ end
+
+ context 'pagination' do
+ shared_context 'setup pipeline records' do
+ let!(:pipelines) do
+ create_list(:package_build_info, 21, :with_pipeline, package: package1)
+ end
+ end
+
+ shared_examples 'returns the default number of pipelines' do
+ it do
+ subject
+
+ expect(json_response.size).to eq(20)
+ end
+ end
+
+ shared_examples 'returns an error about the invalid per_page value' do
+ it do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to match(/per_page does not have a valid value/)
+ end
+ end
+
+ context 'without pagination params' do
+ include_context 'setup pipeline records'
+
+ it_behaves_like 'returns the default number of pipelines'
+ end
+
+ context 'with valid per_page value' do
+ let(:per_page) { 11 }
+
+ subject { get api(package_pipelines_url, user), params: { per_page: per_page } }
+
+ include_context 'setup pipeline records'
+
+ it 'returns the correct number of pipelines' do
+ subject
+
+ expect(json_response.size).to eq(per_page)
+ end
+ end
+
+ context 'with invalid pagination params' do
+ subject { get api(package_pipelines_url, user), params: { per_page: per_page } }
+
+ context 'with non-positive per_page' do
+ let(:per_page) { -2 }
+
+ it_behaves_like 'returns an error about the invalid per_page value'
+ end
+
+ context 'with a too high value for per_page' do
+ let(:per_page) { 21 }
+
+ it_behaves_like 'returns an error about the invalid per_page value'
+ end
+ end
+
+ context 'with valid pagination params' do
+ let_it_be(:package1) { create(:npm_package, :last_downloaded_at, project: project) }
+ let_it_be(:build_info1) { create(:package_build_info, :with_pipeline, package: package1) }
+ let_it_be(:build_info2) { create(:package_build_info, :with_pipeline, package: package1) }
+ let_it_be(:build_info3) { create(:package_build_info, :with_pipeline, package: package1) }
+
+ let(:pipeline1) { build_info1.pipeline }
+ let(:pipeline2) { build_info2.pipeline }
+ let(:pipeline3) { build_info3.pipeline }
+
+ let(:per_page) { 2 }
+
+ context 'with no cursor supplied' do
+ subject { get api(package_pipelines_url, user), params: { per_page: per_page } }
+
+ it 'returns first 2 pipelines' do
+ subject
+
+ expect(json_response.pluck('id')).to contain_exactly(pipeline3.id, pipeline2.id)
+ end
+ end
+
+ context 'with a cursor parameter' do
+ let(:cursor) { Base64.urlsafe_encode64(Gitlab::Json.dump(cursor_attributes)) }
+
+ subject { get api(package_pipelines_url, user), params: { per_page: per_page, cursor: cursor } }
+
+ before do
+ subject
+ end
+
+ context 'with a cursor for the next page' do
+ let(:cursor_attributes) { { "id" => build_info2.id, "_kd" => "n" } }
+
+ it 'returns the next page of records' do
+ expect(json_response.pluck('id')).to contain_exactly(pipeline1.id)
+ end
+ end
+
+ context 'with a cursor for the previous page' do
+ let(:cursor_attributes) { { "id" => build_info1.id, "_kd" => "p" } }
+
+ it 'returns the previous page of records' do
+ expect(json_response.pluck('id')).to contain_exactly(pipeline3.id, pipeline2.id)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
describe 'DELETE /projects/:id/packages/:package_id' do
context 'without the need for a license' do
context 'project is public' do
@@ -379,7 +610,7 @@ RSpec.describe API::ProjectPackages, feature_category: :package_registry do
end
context 'project is private' do
- let(:project) { create(:project, :private) }
+ let_it_be(:project) { create(:project, :private) }
before do
expect(::Packages::Maven::Metadata::SyncWorker).not_to receive(:perform_async)
diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb
index 91e5ed76c37..e1d156afd54 100644
--- a/spec/requests/api/project_templates_spec.rb
+++ b/spec/requests/api/project_templates_spec.rb
@@ -63,27 +63,6 @@ RSpec.describe API::ProjectTemplates, feature_category: :source_code_management
expect(json_response).to satisfy_one { |template| template['key'] == 'mit' }
end
- it 'returns metrics_dashboard_ymls' do
- get api("/projects/#{public_project.id}/templates/metrics_dashboard_ymls")
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(response).to match_response_schema('public_api/v4/template_list')
- expect(json_response).to satisfy_one { |template| template['key'] == 'Default' }
- end
-
- context 'when metrics dashboard feature is unavailable' do
- before do
- stub_feature_flags(remove_monitor_metrics: true)
- end
-
- it 'returns 400 bad request like other unknown types' do
- get api("/projects/#{public_project.id}/templates/metrics_dashboard_ymls")
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
it 'returns issue templates' do
get api("/projects/#{private_project.id}/templates/issues", developer)
@@ -176,26 +155,6 @@ RSpec.describe API::ProjectTemplates, feature_category: :source_code_management
expect(json_response['name']).to eq('Android')
end
- it 'returns a specific metrics_dashboard_yml' do
- get api("/projects/#{public_project.id}/templates/metrics_dashboard_ymls/Default")
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/template')
- expect(json_response['name']).to eq('Default')
- end
-
- context 'when metrics dashboard feature is unavailable' do
- before do
- stub_feature_flags(remove_monitor_metrics: true)
- end
-
- it 'returns 400 bad request like other unknown types' do
- get api("/projects/#{public_project.id}/templates/metrics_dashboard_ymls/Default")
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
it 'returns a specific license' do
get api("/projects/#{public_project.id}/templates/licenses/mit")
@@ -256,10 +215,6 @@ RSpec.describe API::ProjectTemplates, feature_category: :source_code_management
subject { get api("/projects/#{url_encoded_path}/templates/gitlab_ci_ymls/Android") }
end
- it_behaves_like 'accepts project paths with dots' do
- subject { get api("/projects/#{url_encoded_path}/templates/metrics_dashboard_ymls/Default") }
- end
-
shared_examples 'path traversal attempt' do |template_type|
it 'rejects invalid filenames' do
get api("/projects/#{public_project.id}/templates/#{template_type}/%2e%2e%2fPython%2ea")
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 349101a092f..bb96771b3d5 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -46,7 +46,7 @@ RSpec.shared_examples 'languages and percentages JSON response' do
end
end
-RSpec.describe API::Projects, :aggregate_failures, feature_category: :projects do
+RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and_projects do
include ProjectForksHelper
include WorkhorseHelpers
include StubRequests
@@ -2158,7 +2158,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :projects d
end
shared_examples 'capped upload attachments' do |upload_allowed|
- it "limits the upload to 1 GB" do
+ it "limits the upload to 1 GiB" do
expect_next_instance_of(UploadService) do |instance|
expect(instance).to receive(:override_max_attachment_size=).with(1.gigabyte).and_call_original
end
@@ -5154,7 +5154,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :projects d
it 'includes groups where the user has permissions to transfer a project to' do
request
- expect(project_ids_from_response).to include(maintainer_group.id, owner_group.id)
+ expect(project_ids_from_response).to match_array [maintainer_group.id, owner_group.id]
end
it 'does not include groups where the user doesn not have permissions to transfer a project' do
@@ -5163,6 +5163,12 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :projects d
expect(project_ids_from_response).not_to include(guest_group.id)
end
+ it 'does not include the group id of the current project' do
+ request
+
+ expect(project_ids_from_response).not_to include(project.group.id)
+ end
+
context 'with search' do
let(:params) { { search: 'maintainer' } }
diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb
index b8c10de2302..3420e38f4af 100644
--- a/spec/requests/api/release/links_spec.rb
+++ b/spec/requests/api/release/links_spec.rb
@@ -5,12 +5,12 @@ require 'spec_helper'
RSpec.describe API::Release::Links, feature_category: :release_orchestration do
include Ci::JobTokenScopeHelpers
- let(:project) { create(:project, :repository, :private) }
- let(:maintainer) { create(:user) }
- let(:developer) { create(:user) }
- let(:reporter) { create(:user) }
- let(:non_project_member) { create(:user) }
- let(:commit) { create(:commit, project: project) }
+ let_it_be_with_reload(:project) { create(:project, :repository, :private) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:non_project_member) { create(:user) }
+ let_it_be(:commit) { create(:commit, project: project) }
let!(:release) do
create(:release,
@@ -19,7 +19,7 @@ RSpec.describe API::Release::Links, feature_category: :release_orchestration do
author: maintainer)
end
- before do
+ before_all do
project.add_maintainer(maintainer)
project.add_developer(developer)
project.add_reporter(reporter)
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index 0b5cc3611bd..a018b91019b 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -791,16 +791,16 @@ RSpec.describe API::Releases, :aggregate_failures, feature_category: :release_or
name: 'New release',
tag_name: 'v0.1',
description: 'Super nice release',
- assets: {
- links: [
- {
- name: 'An example runbook link',
- url: 'https://example.com/runbook',
- link_type: 'runbook',
- filepath: '/permanent/path/to/runbook'
- }
- ]
- }
+ assets: { links: [link_asset] }
+ }
+ end
+
+ let(:link_asset) do
+ {
+ name: 'An example runbook link',
+ url: 'https://example.com/runbook',
+ link_type: 'runbook',
+ filepath: '/permanent/path/to/runbook'
}
end
@@ -906,8 +906,13 @@ RSpec.describe API::Releases, :aggregate_failures, feature_category: :release_or
end
context 'when using `direct_asset_path` for the asset link' do
- before do
- params[:direct_asset_path] = params.delete(:filepath)
+ let(:link_asset) do
+ {
+ name: 'An example runbook link',
+ url: 'https://example.com/runbook',
+ link_type: 'runbook',
+ direct_asset_path: '/permanent/path/to/runbook'
+ }
end
it 'creates a new release successfully' do
@@ -915,8 +920,9 @@ RSpec.describe API::Releases, :aggregate_failures, feature_category: :release_or
post api("/projects/#{project.id}/releases", maintainer), params: params
end.to change { Release.count }.by(1)
- release = project.releases.last
+ expect(response).to have_gitlab_http_status(:created)
+ release = project.releases.last
expect(release.links.last.filepath).to eq('/permanent/path/to/runbook')
end
end
diff --git a/spec/requests/api/resource_access_tokens_spec.rb b/spec/requests/api/resource_access_tokens_spec.rb
index ce05fa2b383..dcb6572d413 100644
--- a/spec/requests/api/resource_access_tokens_spec.rb
+++ b/spec/requests/api/resource_access_tokens_spec.rb
@@ -336,32 +336,15 @@ RSpec.describe API::ResourceAccessTokens, feature_category: :system_access do
context "when 'expires_at' is not set" do
let(:expires_at) { nil }
- context 'when default_pat_expiration feature flag is true' do
- it "creates a #{source_type} access token with the default expires_at value", :aggregate_failures do
- freeze_time do
- create_token
- expires_at = PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
-
- expect(response).to have_gitlab_http_status(:created)
- expect(json_response["name"]).to eq("test")
- expect(json_response["scopes"]).to eq(["api"])
- expect(json_response["expires_at"]).to eq(expires_at.to_date.iso8601)
- end
- end
- end
-
- context 'when default_pat_expiration feature flag is false' do
- before do
- stub_feature_flags(default_pat_expiration: false)
- end
-
- it "creates a #{source_type} access token with the params", :aggregate_failures do
+ it "creates a #{source_type} access token with the default expires_at value", :aggregate_failures do
+ freeze_time do
create_token
+ expires_at = PersonalAccessToken::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
expect(response).to have_gitlab_http_status(:created)
expect(json_response["name"]).to eq("test")
expect(json_response["scopes"]).to eq(["api"])
- expect(json_response["expires_at"]).to eq(nil)
+ expect(json_response["expires_at"]).to eq(expires_at.to_date.iso8601)
end
end
end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index a315bca58d1..1b331e9c099 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -412,6 +412,22 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
end
end
+ context 'global snippet search is disabled' do
+ it 'returns forbidden response' do
+ stub_feature_flags(global_search_snippet_titles_tab: false)
+ get api(endpoint, user), params: { search: 'awesome', scope: 'snippet_titles' }
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'global snippet search is enabled' do
+ it 'returns ok response' do
+ stub_feature_flags(global_search_snippet_titles_tab: true)
+ get api(endpoint, user), params: { search: 'awesome', scope: 'snippet_titles' }
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
it 'increments the custom search sli error rate with error false if no error occurred' do
expect(Gitlab::Metrics::GlobalSearchSlis).to receive(:record_error_rate).with(
error: false,
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 3f66cbaf2b7..79e96d7ea3e 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -19,6 +19,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
expect(json_response['password_authentication_enabled']).to be_truthy
expect(json_response['plantuml_enabled']).to be_falsey
expect(json_response['plantuml_url']).to be_nil
+ expect(json_response['diagramsnet_enabled']).to be_truthy
+ expect(json_response['diagramsnet_url']).to eq('https://embed.diagrams.net')
expect(json_response['default_ci_config_path']).to be_nil
expect(json_response['sourcegraph_enabled']).to be_falsey
expect(json_response['sourcegraph_url']).to be_nil
@@ -46,6 +48,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
expect(json_response['spam_check_endpoint_url']).to be_nil
expect(json_response['spam_check_api_key']).to be_nil
expect(json_response['wiki_page_max_content_bytes']).to be_a(Integer)
+ expect(json_response['wiki_asciidoc_allow_uri_includes']).to be_falsey
expect(json_response['require_admin_approval_after_user_signup']).to eq(true)
expect(json_response['personal_access_token_prefix']).to eq('glpat-')
expect(json_response['admin_mode']).to be(false)
@@ -76,6 +79,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
expect(json_response['slack_app_verification_token']).to be_nil
expect(json_response['valid_runner_registrars']).to match_array(%w(project group))
expect(json_response['ci_max_includes']).to eq(150)
+ expect(json_response['allow_account_deletion']).to eq(true)
end
end
@@ -123,6 +127,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
repository_storages_weighted: { 'custom' => 100 },
plantuml_enabled: true,
plantuml_url: 'http://plantuml.example.com',
+ diagramsnet_enabled: false,
+ diagramsnet_url: nil,
sourcegraph_enabled: true,
sourcegraph_url: 'https://sourcegraph.com',
sourcegraph_public_only: false,
@@ -165,6 +171,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
disabled_oauth_sign_in_sources: 'unknown',
import_sources: 'github,bitbucket',
wiki_page_max_content_bytes: 12345,
+ wiki_asciidoc_allow_uri_includes: true,
personal_access_token_prefix: "GL-",
user_deactivation_emails_enabled: false,
admin_mode: true,
@@ -188,7 +195,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
slack_app_secret: 'SLACK_APP_SECRET',
slack_app_signing_secret: 'SLACK_APP_SIGNING_SECRET',
slack_app_verification_token: 'SLACK_APP_VERIFICATION_TOKEN',
- valid_runner_registrars: ['group']
+ valid_runner_registrars: ['group'],
+ allow_account_deletion: false
}
expect(response).to have_gitlab_http_status(:ok)
@@ -199,6 +207,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
expect(json_response['repository_storages_weighted']).to eq({ 'custom' => 100 })
expect(json_response['plantuml_enabled']).to be_truthy
expect(json_response['plantuml_url']).to eq('http://plantuml.example.com')
+ expect(json_response['diagramsnet_enabled']).to be_falsey
+ expect(json_response['diagramsnet_url']).to be_nil
expect(json_response['sourcegraph_enabled']).to be_truthy
expect(json_response['sourcegraph_url']).to eq('https://sourcegraph.com')
expect(json_response['sourcegraph_public_only']).to eq(false)
@@ -241,6 +251,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
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)
+ expect(json_response['wiki_asciidoc_allow_uri_includes']).to be(true)
expect(json_response['personal_access_token_prefix']).to eq("GL-")
expect(json_response['admin_mode']).to be(true)
expect(json_response['user_deactivation_emails_enabled']).to be(false)
@@ -265,6 +276,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
expect(json_response['slack_app_signing_secret']).to eq('SLACK_APP_SIGNING_SECRET')
expect(json_response['slack_app_verification_token']).to eq('SLACK_APP_VERIFICATION_TOKEN')
expect(json_response['valid_runner_registrars']).to eq(['group'])
+ expect(json_response['allow_account_deletion']).to be(false)
end
end
@@ -547,6 +559,15 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu
end
end
+ context "missing diagramsnet_url value when diagramsnet_enabled is true" do
+ it "returns a blank parameter error message" do
+ put api("/application/settings", admin), params: { diagramsnet_enabled: true }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eq('diagramsnet_url is missing')
+ end
+ end
+
context 'asset_proxy settings' do
it 'updates application settings' do
put api('/application/settings', admin),
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 51edf4b3b3e..16912fd279b 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, feature_category: :integrations do
+RSpec.describe API::SystemHooks, feature_category: :webhooks 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/topics_spec.rb b/spec/requests/api/topics_spec.rb
index 560f22c94be..0d64a96acb8 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, :aggregate_failures, feature_category: :projects do
+RSpec.describe API::Topics, :aggregate_failures, feature_category: :groups_and_projects do
include WorkhorseHelpers
let_it_be(:file) { fixture_file_upload('spec/fixtures/dk.png') }
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 b2929caf676..4ca6c5cace3 100644
--- a/spec/requests/api/usage_data_non_sql_metrics_spec.rb
+++ b/spec/requests/api/usage_data_non_sql_metrics_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe API::UsageDataNonSqlMetrics, :aggregate_failures, feature_categor
stub_usage_data_connections
end
- describe 'GET /usage_data/non_sql_metrics' do
+ describe 'GET /usage_data/non_sql_metrics', :with_license do
let(:endpoint) { '/usage_data/non_sql_metrics' }
context 'with authentication' do
diff --git a/spec/requests/api/usage_data_queries_spec.rb b/spec/requests/api/usage_data_queries_spec.rb
index ab3c38adb81..584b0f31a07 100644
--- a/spec/requests/api/usage_data_queries_spec.rb
+++ b/spec/requests/api/usage_data_queries_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe API::UsageDataQueries, :aggregate_failures, feature_category: :se
stub_database_flavor_check
end
- describe 'GET /usage_data/usage_data_queries' do
+ describe 'GET /usage_data/usage_data_queries', :with_license do
let(:endpoint) { '/usage_data/queries' }
context 'with authentication' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index cc8be312c71..3737c91adbc 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -3480,7 +3480,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
activate
expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response['message']).to eq('403 Forbidden - A blocked user must be unblocked to be activated')
+ expect(json_response['message']).to eq('Error occurred. A blocked user must be unblocked to be activated')
expect(blocked_user.reload.state).to eq('blocked')
end
end
@@ -3494,7 +3494,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
activate
expect(response).to have_gitlab_http_status(:forbidden)
- expect(json_response['message']).to eq('403 Forbidden - A blocked user must be unblocked to be activated')
+ expect(json_response['message']).to eq('Error occurred. A blocked user must be unblocked to be activated')
expect(user.reload.state).to eq('ldap_blocked')
end
end
@@ -4516,7 +4516,7 @@ RSpec.describe API::Users, :aggregate_failures, feature_category: :user_profile
post api(path, admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['error']).to eq('name is missing, scopes is missing, scopes does not have a valid value')
+ expect(json_response['error']).to eq('name is missing, scopes is missing')
end
it 'returns a 404 error if user not found' do
diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb
index b6fccd9b7cb..fbda291e901 100644
--- a/spec/requests/api/v3/github_spec.rb
+++ b/spec/requests/api/v3/github_spec.rb
@@ -13,16 +13,33 @@ RSpec.describe API::V3::Github, :aggregate_failures, feature_category: :integrat
end
describe 'GET /orgs/:namespace/repos' do
+ let_it_be(:group) { create(:group) }
+
it_behaves_like 'a GitHub Enterprise Jira DVCS reversible end of life endpoint' do
subject do
- group = create(:group)
jira_get v3_api("/orgs/#{group.path}/repos", user)
end
end
- it 'returns an empty array' do
- group = create(:group)
+ it 'logs when the endpoint is hit and `jira_dvcs_end_of_life_amnesty` is enabled' do
+ expect(Gitlab::JsonLogger).to receive(:info).with(
+ including(
+ namespace: group.path,
+ user_id: user.id,
+ message: 'Deprecated Jira DVCS endpoint request'
+ )
+ )
+
+ jira_get v3_api("/orgs/#{group.path}/repos", user)
+
+ stub_feature_flags(jira_dvcs_end_of_life_amnesty: false)
+ expect(Gitlab::JsonLogger).not_to receive(:info)
+
+ jira_get v3_api("/orgs/#{group.path}/repos", user)
+ end
+
+ it 'returns an empty array' do
jira_get v3_api("/orgs/#{group.path}/repos", user)
expect(response).to have_gitlab_http_status(:ok)