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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests')
-rw-r--r--spec/requests/admin/background_migrations_controller_spec.rb8
-rw-r--r--spec/requests/api/admin/instance_clusters_spec.rb20
-rw-r--r--spec/requests/api/broadcast_messages_spec.rb23
-rw-r--r--spec/requests/api/ci/jobs_spec.rb51
-rw-r--r--spec/requests/api/ci/pipelines_spec.rb17
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb6
-rw-r--r--spec/requests/api/ci/runner/jobs_trace_spec.rb8
-rw-r--r--spec/requests/api/ci/runner/runners_post_spec.rb8
-rw-r--r--spec/requests/api/ci/runner/runners_reset_spec.rb65
-rw-r--r--spec/requests/api/ci/runners_spec.rb8
-rw-r--r--spec/requests/api/ci/secure_files_spec.rb187
-rw-r--r--spec/requests/api/commits_spec.rb9
-rw-r--r--spec/requests/api/container_repositories_spec.rb64
-rw-r--r--spec/requests/api/deploy_tokens_spec.rb106
-rw-r--r--spec/requests/api/error_tracking/collector_spec.rb15
-rw-r--r--spec/requests/api/error_tracking/project_settings_spec.rb59
-rw-r--r--spec/requests/api/generic_packages_spec.rb11
-rw-r--r--spec/requests/api/graphql/ci/pipelines_spec.rb33
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb33
-rw-r--r--spec/requests/api/graphql/ci/runner_web_url_edge_spec.rb57
-rw-r--r--spec/requests/api/graphql/container_repository/container_repository_details_spec.rb80
-rw-r--r--spec/requests/api/graphql/group/group_members_spec.rb46
-rw-r--r--spec/requests/api/graphql/group/issues_spec.rb25
-rw-r--r--spec/requests/api/graphql/group/merge_requests_spec.rb21
-rw-r--r--spec/requests/api/graphql/group/work_item_types_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb33
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb87
-rw-r--r--spec/requests/api/graphql/namespace_query_spec.rb3
-rw-r--r--spec/requests/api/graphql/project/jira_service_spec.rb9
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb18
-rw-r--r--spec/requests/api/graphql/project/work_item_types_spec.rb4
-rw-r--r--spec/requests/api/graphql/query_spec.rb24
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb75
-rw-r--r--spec/requests/api/group_clusters_spec.rb20
-rw-r--r--spec/requests/api/group_labels_spec.rb10
-rw-r--r--spec/requests/api/integrations_spec.rb10
-rw-r--r--spec/requests/api/internal/kubernetes_spec.rb1
-rw-r--r--spec/requests/api/internal/mail_room_spec.rb16
-rw-r--r--spec/requests/api/invitations_spec.rb21
-rw-r--r--spec/requests/api/issues/post_projects_issues_spec.rb2
-rw-r--r--spec/requests/api/issues/put_projects_issues_spec.rb22
-rw-r--r--spec/requests/api/labels_spec.rb22
-rw-r--r--spec/requests/api/members_spec.rb6
-rw-r--r--spec/requests/api/notes_spec.rb89
-rw-r--r--spec/requests/api/project_attributes.yml2
-rw-r--r--spec/requests/api/project_clusters_spec.rb20
-rw-r--r--spec/requests/api/project_import_spec.rb87
-rw-r--r--spec/requests/api/project_snippets_spec.rb4
-rw-r--r--spec/requests/api/projects_spec.rb34
-rw-r--r--spec/requests/api/pypi_packages_spec.rb8
-rw-r--r--spec/requests/api/releases_spec.rb6
-rw-r--r--spec/requests/api/repositories_spec.rb7
-rw-r--r--spec/requests/api/search_spec.rb11
-rw-r--r--spec/requests/api/snippets_spec.rb4
-rw-r--r--spec/requests/api/system_hooks_spec.rb49
-rw-r--r--spec/requests/api/terraform/state_spec.rb4
-rw-r--r--spec/requests/api/topics_spec.rb52
-rw-r--r--spec/requests/api/user_counts_spec.rb15
-rw-r--r--spec/requests/api/users_spec.rb58
-rw-r--r--spec/requests/api/wikis_spec.rb54
-rw-r--r--spec/requests/content_security_policy_spec.rb50
-rw-r--r--spec/requests/git_http_spec.rb48
-rw-r--r--spec/requests/groups/crm/contacts_controller_spec.rb6
-rw-r--r--spec/requests/groups/crm/organizations_controller_spec.rb6
-rw-r--r--spec/requests/groups/deploy_tokens_controller_spec.rb40
-rw-r--r--spec/requests/groups/harbor/repositories_controller_spec.rb69
-rw-r--r--spec/requests/jira_connect/oauth_callbacks_controller_spec.rb22
-rw-r--r--spec/requests/projects/google_cloud/deployments_controller_spec.rb71
-rw-r--r--spec/requests/projects/google_cloud/gcp_regions_controller_spec.rb152
-rw-r--r--spec/requests/projects/google_cloud/revoke_oauth_controller_spec.rb86
-rw-r--r--spec/requests/projects/google_cloud/service_accounts_controller_spec.rb105
-rw-r--r--spec/requests/projects/google_cloud_controller_spec.rb78
-rw-r--r--spec/requests/projects/harbor/repositories_controller_spec.rb69
-rw-r--r--spec/requests/projects/redirect_controller_spec.rb66
75 files changed, 2411 insertions, 322 deletions
diff --git a/spec/requests/admin/background_migrations_controller_spec.rb b/spec/requests/admin/background_migrations_controller_spec.rb
index 67c9c4df827..55971a00e55 100644
--- a/spec/requests/admin/background_migrations_controller_spec.rb
+++ b/spec/requests/admin/background_migrations_controller_spec.rb
@@ -16,7 +16,13 @@ RSpec.describe Admin::BackgroundMigrationsController, :enable_admin_mode do
create(:batched_background_migration_job, :failed, batched_migration: migration, batch_size: 10, min_value: 6, max_value: 15, attempts: 3)
allow_next_instance_of(Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy) do |batch_class|
- allow(batch_class).to receive(:next_batch).with(anything, anything, batch_min_value: 6, batch_size: 5).and_return([6, 10])
+ allow(batch_class).to receive(:next_batch).with(
+ anything,
+ anything,
+ batch_min_value: 6,
+ batch_size: 5,
+ job_arguments: migration.job_arguments
+ ).and_return([6, 10])
end
end
diff --git a/spec/requests/api/admin/instance_clusters_spec.rb b/spec/requests/api/admin/instance_clusters_spec.rb
index ab3b6b718e1..7b3224f58c5 100644
--- a/spec/requests/api/admin/instance_clusters_spec.rb
+++ b/spec/requests/api/admin/instance_clusters_spec.rb
@@ -21,6 +21,10 @@ RSpec.describe ::API::Admin::InstanceClusters do
create_list(:cluster, 3, :provided_by_gcp, :instance, :production_environment)
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { get api("/admin/clusters", admin_user) }
+ end
+
context "when authenticated as a non-admin user" do
it 'returns 403' do
get api('/admin/clusters', regular_user)
@@ -62,6 +66,10 @@ RSpec.describe ::API::Admin::InstanceClusters do
let(:cluster_id) { cluster.id }
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { get api("/admin/clusters/#{cluster_id}", admin_user) }
+ end
+
context "when authenticated as admin" do
before do
get api("/admin/clusters/#{cluster_id}", admin_user)
@@ -188,6 +196,10 @@ RSpec.describe ::API::Admin::InstanceClusters do
}
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { post api('/admin/clusters/add', admin_user), params: cluster_params }
+ end
+
context 'authorized user' do
before do
post api('/admin/clusters/add', admin_user), params: cluster_params
@@ -317,6 +329,10 @@ RSpec.describe ::API::Admin::InstanceClusters do
create(:cluster, :instance, :provided_by_gcp, domain: 'old-domain.com')
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { put api("/admin/clusters/#{cluster.id}", admin_user), params: update_params }
+ end
+
context 'authorized user' do
before do
put api("/admin/clusters/#{cluster.id}", admin_user), params: update_params
@@ -448,6 +464,10 @@ RSpec.describe ::API::Admin::InstanceClusters do
create(:cluster, :instance, :provided_by_gcp)
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { delete api("/admin/clusters/#{cluster.id}", admin_user), params: cluster_params }
+ end
+
context 'authorized user' do
before do
delete api("/admin/clusters/#{cluster.id}", admin_user), params: cluster_params
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index b023ec398a2..76412c80f4c 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe API::BroadcastMessages do
expect(response).to include_pagination_headers
expect(json_response).to be_kind_of(Array)
expect(json_response.first.keys)
- .to match_array(%w(id message starts_at ends_at color font active target_path broadcast_type dismissable))
+ .to match_array(%w(id message starts_at ends_at color font active target_access_levels target_path broadcast_type dismissable))
end
end
@@ -28,7 +28,7 @@ RSpec.describe API::BroadcastMessages do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq message.id
expect(json_response.keys)
- .to match_array(%w(id message starts_at ends_at color font active target_path broadcast_type dismissable))
+ .to match_array(%w(id message starts_at ends_at color font active target_access_levels target_path broadcast_type dismissable))
end
end
@@ -77,6 +77,16 @@ RSpec.describe API::BroadcastMessages do
expect(json_response['font']).to eq attrs[:font]
end
+ it 'accepts target access levels' do
+ target_access_levels = [Gitlab::Access::GUEST, Gitlab::Access::DEVELOPER]
+ attrs = attributes_for(:broadcast_message, target_access_levels: target_access_levels)
+
+ post api('/broadcast_messages', admin), params: attrs
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['target_access_levels']).to eq attrs[:target_access_levels]
+ end
+
it 'accepts a target path' do
attrs = attributes_for(:broadcast_message, target_path: "*/welcome")
@@ -171,6 +181,15 @@ RSpec.describe API::BroadcastMessages do
expect { message.reload }.to change { message.message }.to('new message')
end
+ it 'accepts a new target_access_levels' do
+ attrs = { target_access_levels: [Gitlab::Access::MAINTAINER] }
+
+ put api("/broadcast_messages/#{message.id}", admin), params: attrs
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['target_access_levels']).to eq attrs[:target_access_levels]
+ end
+
it 'accepts a new target_path' do
attrs = { target_path: '*/welcome' }
diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb
index 7c85cbc31a5..f6dae7e8e23 100644
--- a/spec/requests/api/ci/jobs_spec.rb
+++ b/spec/requests/api/ci/jobs_spec.rb
@@ -707,12 +707,14 @@ RSpec.describe API::Ci::Jobs do
end
describe 'POST /projects/:id/jobs/:job_id/play' do
+ let(:params) { {} }
+
before do
- post api("/projects/#{project.id}/jobs/#{job.id}/play", api_user)
+ post api("/projects/#{project.id}/jobs/#{job.id}/play", api_user), params: params
end
context 'on a playable job' do
- let_it_be(:job) { create(:ci_bridge, :playable, pipeline: pipeline, downstream: project) }
+ let_it_be(:job) { create(:ci_build, :manual, project: project, pipeline: pipeline) }
before do
project.add_developer(user)
@@ -720,6 +722,8 @@ RSpec.describe API::Ci::Jobs do
context 'when user is authorized to trigger a manual action' do
context 'that is a bridge' do
+ let_it_be(:job) { create(:ci_bridge, :playable, pipeline: pipeline, downstream: project) }
+
it 'plays the job' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['user']['id']).to eq(user.id)
@@ -729,8 +733,6 @@ RSpec.describe API::Ci::Jobs do
end
context 'that is a build' do
- let_it_be(:job) { create(:ci_build, :manual, project: project, pipeline: pipeline) }
-
it 'plays the job' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['user']['id']).to eq(user.id)
@@ -738,6 +740,47 @@ RSpec.describe API::Ci::Jobs do
expect(job.reload).to be_pending
end
end
+
+ context 'when the user provides valid custom variables' do
+ let(:params) { { job_variables_attributes: [{ key: 'TEST_VAR', value: 'test' }] } }
+
+ it 'applies the variables to the job' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(job.reload).to be_pending
+ expect(job.job_variables.map(&:key)).to contain_exactly('TEST_VAR')
+ expect(job.job_variables.map(&:value)).to contain_exactly('test')
+ end
+ end
+
+ context 'when the user provides a variable without a key' do
+ let(:params) { { job_variables_attributes: [{ value: 'test' }] } }
+
+ it 'reports that the key is missing' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eq('job_variables_attributes[0][key] is missing')
+ expect(job.reload).to be_manual
+ end
+ end
+
+ context 'when the user provides a variable without a value' do
+ let(:params) { { job_variables_attributes: [{ key: 'TEST_VAR' }] } }
+
+ it 'reports that the value is missing' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eq('job_variables_attributes[0][value] is missing')
+ expect(job.reload).to be_manual
+ end
+ end
+
+ context 'when the user provides both valid and invalid variables' do
+ let(:params) { { job_variables_attributes: [{ key: 'TEST_VAR', value: 'test' }, { value: 'test2' }] } }
+
+ it 'reports the invalid variables and does not run the job' do
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eq('job_variables_attributes[1][key] is missing')
+ expect(job.reload).to be_manual
+ end
+ end
end
context 'when user is not authorized to trigger a manual action' do
diff --git a/spec/requests/api/ci/pipelines_spec.rb b/spec/requests/api/ci/pipelines_spec.rb
index 1b87a5e24f5..12faeec94da 100644
--- a/spec/requests/api/ci/pipelines_spec.rb
+++ b/spec/requests/api/ci/pipelines_spec.rb
@@ -1075,6 +1075,23 @@ RSpec.describe API::Ci::Pipelines do
expect(json_response['id']).to be nil
end
end
+
+ context 'handles errors' do
+ before do
+ service_response = ServiceResponse.error(http_status: 403, message: 'hello world')
+ allow_next_instance_of(::Ci::RetryPipelineService) do |service|
+ allow(service).to receive(:check_access).and_return(service_response)
+ end
+ end
+
+ it 'returns error' do
+ post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq 'hello world'
+ expect(json_response['id']).to be nil
+ end
+ end
end
describe 'POST /projects/:id/pipelines/:pipeline_id/cancel' do
diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
index 68f7581bf06..d317386dc73 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -156,7 +156,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
'sha' => job.sha,
'before_sha' => job.before_sha,
'ref_type' => 'branch',
- 'refspecs' => ["+#{pipeline.sha}:refs/pipelines/#{pipeline.id}",
+ 'refspecs' => ["+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
"+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}"],
'depth' => project.ci_default_git_depth }
end
@@ -291,7 +291,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['git_info']['refspecs'])
- .to contain_exactly("+#{pipeline.sha}:refs/pipelines/#{pipeline.id}",
+ .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
'+refs/tags/*:refs/tags/*',
'+refs/heads/*:refs/remotes/origin/*')
end
@@ -359,7 +359,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['git_info']['refspecs'])
- .to contain_exactly("+#{pipeline.sha}:refs/pipelines/#{pipeline.id}",
+ .to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
'+refs/tags/*:refs/tags/*',
'+refs/heads/*:refs/remotes/origin/*')
end
diff --git a/spec/requests/api/ci/runner/jobs_trace_spec.rb b/spec/requests/api/ci/runner/jobs_trace_spec.rb
index 2760e306693..d6928969beb 100644
--- a/spec/requests/api/ci/runner/jobs_trace_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_trace_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks do
let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token, 'Content-Type' => 'text/plain' } }
let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
- let(:update_interval) { 10.seconds.to_i }
+ let(:update_interval) { 10.seconds }
before do
initial_patch_the_trace
@@ -81,7 +81,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks do
end
context 'when job was not updated recently' do
- let(:update_interval) { 15.minutes.to_i }
+ let(:update_interval) { 16.minutes }
it { expect { patch_the_trace }.to change { job.updated_at } }
@@ -293,10 +293,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_trace_chunks do
end
end
- Timecop.travel(job.updated_at + update_interval) do
+ travel_to(job.updated_at + update_interval) do
patch api("/jobs/#{job_id}/trace"), params: content, headers: request_headers
- job.reload
end
+ job.reload
end
def initial_patch_the_trace
diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb
index 5eb5d3977a3..1d553751eea 100644
--- a/spec/requests/api/ci/runner/runners_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_post_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
context 'when invalid token is provided' do
it 'returns 403 error' do
- allow_next_instance_of(::Ci::RegisterRunnerService) do |service|
+ allow_next_instance_of(::Ci::Runners::RegisterRunnerService) do |service|
allow(service).to receive(:execute).and_return(nil)
end
@@ -43,7 +43,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let_it_be(:new_runner) { create(:ci_runner) }
before do
- allow_next_instance_of(::Ci::RegisterRunnerService) do |service|
+ allow_next_instance_of(::Ci::Runners::RegisterRunnerService) do |service|
expected_params = {
description: 'server.hostname',
maintenance_note: 'Some maintainer notes',
@@ -108,7 +108,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let(:new_runner) { create(:ci_runner) }
it 'converts to maintenance_note param' do
- allow_next_instance_of(::Ci::RegisterRunnerService) do |service|
+ allow_next_instance_of(::Ci::Runners::RegisterRunnerService) do |service|
expect(service).to receive(:execute)
.once
.with('valid token', a_hash_including('maintenance_note' => 'Some maintainer notes')
@@ -133,7 +133,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let_it_be(:new_runner) { create(:ci_runner) }
it 'uses active value in registration' do
- expect_next_instance_of(::Ci::RegisterRunnerService) do |service|
+ expect_next_instance_of(::Ci::Runners::RegisterRunnerService) do |service|
expected_params = { active: false }.stringify_keys
expect(service).to receive(:execute)
diff --git a/spec/requests/api/ci/runner/runners_reset_spec.rb b/spec/requests/api/ci/runner/runners_reset_spec.rb
new file mode 100644
index 00000000000..8a61012ead1
--- /dev/null
+++ b/spec/requests/api/ci/runner/runners_reset_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
+ include StubGitlabCalls
+ include RedisHelpers
+ include WorkhorseHelpers
+
+ before do
+ stub_feature_flags(ci_enable_live_trace: true)
+ stub_feature_flags(runner_registration_control: false)
+ stub_gitlab_calls
+ stub_application_setting(valid_runner_registrars: ApplicationSetting::VALID_RUNNER_REGISTRAR_TYPES)
+ end
+
+ let_it_be(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 5.days.to_i) }
+ let_it_be(:group) { create(:group, namespace_settings: group_settings) }
+ let_it_be(:instance_runner, reload: true) { create(:ci_runner, :instance) }
+ 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
+ context 'current token provided' do
+ it "resets authentication token when token doesn't have an expiration" do
+ expect do
+ post api("/runners/reset_authentication_token"), params: { token: instance_runner.reload.token }
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response).to eq({ 'token' => instance_runner.reload.token, 'token_expires_at' => nil })
+ expect(instance_runner.reload.token_expires_at).to be_nil
+ end.to change { instance_runner.reload.token }
+ end
+
+ it 'resets authentication token when token is not expired' do
+ expect do
+ post api("/runners/reset_authentication_token"), params: { token: group_runner.reload.token }
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response).to eq({ 'token' => group_runner.reload.token, 'token_expires_at' => group_runner.reload.token_expires_at.iso8601(3) })
+ expect(group_runner.reload.token_expires_at).to eq(5.days.from_now)
+ end.to change { group_runner.reload.token }
+ end
+
+ it 'does not reset authentication token when token is expired' do
+ expect do
+ instance_runner.update!(token_expires_at: 1.day.ago)
+ post api("/runners/reset_authentication_token"), params: { token: instance_runner.reload.token }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ instance_runner.update!(token_expires_at: nil)
+ end.not_to change { instance_runner.reload.token }
+ end
+ end
+
+ context 'wrong current token provided' do
+ it 'does not reset authentication token' do
+ expect do
+ post api("/runners/reset_authentication_token"), params: { token: 'garbage' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { instance_runner.reload.token }
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index 336ce70d8d2..a1fda68b77b 100644
--- a/spec/requests/api/ci/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -530,7 +530,7 @@ RSpec.describe API::Ci::Runners do
context 'admin user' do
context 'when runner is shared' do
it 'deletes runner' do
- expect_next_instance_of(Ci::UnregisterRunnerService, shared_runner) do |service|
+ expect_next_instance_of(Ci::Runners::UnregisterRunnerService, shared_runner, admin) do |service|
expect(service).to receive(:execute).once.and_call_original
end
@@ -548,7 +548,7 @@ RSpec.describe API::Ci::Runners do
context 'when runner is not shared' do
it 'deletes used project runner' do
- expect_next_instance_of(Ci::UnregisterRunnerService, project_runner) do |service|
+ expect_next_instance_of(Ci::Runners::UnregisterRunnerService, project_runner, admin) do |service|
expect(service).to receive(:execute).once.and_call_original
end
@@ -561,7 +561,7 @@ RSpec.describe API::Ci::Runners do
end
it 'returns 404 if runner does not exist' do
- allow_next_instance_of(Ci::UnregisterRunnerService) do |service|
+ allow_next_instance_of(Ci::Runners::UnregisterRunnerService) do |service|
expect(service).not_to receive(:execute)
end
@@ -646,7 +646,7 @@ RSpec.describe API::Ci::Runners do
context 'unauthorized user' do
it 'does not delete project runner' do
- allow_next_instance_of(Ci::UnregisterRunnerService) do |service|
+ allow_next_instance_of(Ci::Runners::UnregisterRunnerService) do |service|
expect(service).not_to receive(:execute)
end
diff --git a/spec/requests/api/ci/secure_files_spec.rb b/spec/requests/api/ci/secure_files_spec.rb
index 5cf6999f60a..aa479cb8713 100644
--- a/spec/requests/api/ci/secure_files_spec.rb
+++ b/spec/requests/api/ci/secure_files_spec.rb
@@ -8,49 +8,72 @@ RSpec.describe API::Ci::SecureFiles do
stub_feature_flags(ci_secure_files: true)
end
- let_it_be(:user) { create(:user) }
- let_it_be(:user2) { create(:user) }
- let_it_be(:project) { create(:project, creator_id: user.id) }
- let_it_be(:maintainer) { create(:project_member, :maintainer, user: user, project: project) }
- let_it_be(:developer) { create(:project_member, :developer, user: user2, project: project) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:anonymous) { create(:user) }
+ let_it_be(:project) { create(:project, creator_id: maintainer.id) }
let_it_be(:secure_file) { create(:ci_secure_file, project: project) }
+ before_all do
+ project.add_maintainer(maintainer)
+ project.add_developer(developer)
+ project.add_guest(guest)
+ end
+
describe 'GET /projects/:id/secure_files' do
context 'feature flag' do
it 'returns a 503 when the feature flag is disabled' do
stub_feature_flags(ci_secure_files: false)
- get api("/projects/#{project.id}/secure_files", user)
+ get api("/projects/#{project.id}/secure_files", maintainer)
expect(response).to have_gitlab_http_status(:service_unavailable)
end
it 'returns a 200 when the feature flag is enabled' do
- get api("/projects/#{project.id}/secure_files", user)
+ get api("/projects/#{project.id}/secure_files", maintainer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_a(Array)
+ end
+ end
+
+ context 'authenticated user with admin permissions' do
+ it 'returns project secure files' do
+ get api("/projects/#{project.id}/secure_files", maintainer)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_a(Array)
end
end
- context 'authorized user with proper permissions' do
+ context 'authenticated user with read permissions' do
it 'returns project secure files' do
- get api("/projects/#{project.id}/secure_files", user)
+ get api("/projects/#{project.id}/secure_files", developer)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_a(Array)
end
end
- context 'authorized user with invalid permissions' do
+ context 'authenticated user with guest permissions' do
it 'does not return project secure files' do
- get api("/projects/#{project.id}/secure_files", user2)
+ get api("/projects/#{project.id}/secure_files", guest)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
- context 'unauthorized user' do
+ context 'authenticated user with no permissions' do
+ it 'does not return project secure files' do
+ get api("/projects/#{project.id}/secure_files", anonymous)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
it 'does not return project secure files' do
get api("/projects/#{project.id}/secure_files")
@@ -60,9 +83,9 @@ RSpec.describe API::Ci::SecureFiles do
end
describe 'GET /projects/:id/secure_files/:secure_file_id' do
- context 'authorized user with proper permissions' do
+ context 'authenticated user with admin permissions' do
it 'returns project secure file details' do
- get api("/projects/#{project.id}/secure_files/#{secure_file.id}", user)
+ get api("/projects/#{project.id}/secure_files/#{secure_file.id}", maintainer)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(secure_file.name)
@@ -70,21 +93,31 @@ RSpec.describe API::Ci::SecureFiles do
end
it 'responds with 404 Not Found if requesting non-existing secure file' do
- get api("/projects/#{project.id}/secure_files/99999", user)
+ get api("/projects/#{project.id}/secure_files/#{non_existing_record_id}", maintainer)
expect(response).to have_gitlab_http_status(:not_found)
end
end
- context 'authorized user with invalid permissions' do
+ context 'authenticated user with read permissions' do
+ it 'returns project secure file details' do
+ get api("/projects/#{project.id}/secure_files/#{secure_file.id}", developer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq(secure_file.name)
+ expect(json_response['permissions']).to eq(secure_file.permissions)
+ end
+ end
+
+ context 'authenticated user with no permissions' do
it 'does not return project secure file details' do
- get api("/projects/#{project.id}/secure_files/#{secure_file.id}", user2)
+ get api("/projects/#{project.id}/secure_files/#{secure_file.id}", anonymous)
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
- context 'unauthorized user' do
+ context 'unauthenticated user' do
it 'does not return project secure file details' do
get api("/projects/#{project.id}/secure_files/#{secure_file.id}")
@@ -94,34 +127,47 @@ RSpec.describe API::Ci::SecureFiles do
end
describe 'GET /projects/:id/secure_files/:secure_file_id/download' do
- context 'authorized user with proper permissions' do
+ context 'authenticated user with admin permissions' do
it 'returns a secure file' do
sample_file = fixture_file('ci_secure_files/upload-keystore.jks')
secure_file.file = CarrierWaveStringFile.new(sample_file)
secure_file.save!
- get api("/projects/#{project.id}/secure_files/#{secure_file.id}/download", user)
+ get api("/projects/#{project.id}/secure_files/#{secure_file.id}/download", maintainer)
expect(response).to have_gitlab_http_status(:ok)
expect(Base64.encode64(response.body)).to eq(Base64.encode64(sample_file))
end
it 'responds with 404 Not Found if requesting non-existing secure file' do
- get api("/projects/#{project.id}/secure_files/99999/download", user)
+ get api("/projects/#{project.id}/secure_files/#{non_existing_record_id}/download", maintainer)
expect(response).to have_gitlab_http_status(:not_found)
end
end
- context 'authorized user with invalid permissions' do
+ context 'authenticated user with read permissions' do
+ it 'returns a secure file' do
+ sample_file = fixture_file('ci_secure_files/upload-keystore.jks')
+ secure_file.file = CarrierWaveStringFile.new(sample_file)
+ secure_file.save!
+
+ get api("/projects/#{project.id}/secure_files/#{secure_file.id}/download", developer)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(Base64.encode64(response.body)).to eq(Base64.encode64(sample_file))
+ end
+ end
+
+ context 'authenticated user with no permissions' do
it 'does not return project secure file details' do
- get api("/projects/#{project.id}/secure_files/#{secure_file.id}/download", user2)
+ get api("/projects/#{project.id}/secure_files/#{secure_file.id}/download", anonymous)
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
- context 'unauthorized user' do
+ context 'unauthenticated user' do
it 'does not return project secure file details' do
get api("/projects/#{project.id}/secure_files/#{secure_file.id}/download")
@@ -131,7 +177,7 @@ RSpec.describe API::Ci::SecureFiles do
end
describe 'POST /projects/:id/secure_files' do
- context 'authorized user with proper permissions' do
+ context 'authenticated user with admin permissions' do
it 'creates a secure file' do
params = {
file: fixture_file_upload('spec/fixtures/ci_secure_files/upload-keystore.jks'),
@@ -140,7 +186,7 @@ RSpec.describe API::Ci::SecureFiles do
}
expect do
- post api("/projects/#{project.id}/secure_files", user), params: params
+ post api("/projects/#{project.id}/secure_files", maintainer), params: params
end.to change {project.secure_files.count}.by(1)
expect(response).to have_gitlab_http_status(:created)
@@ -154,6 +200,7 @@ RSpec.describe API::Ci::SecureFiles do
Digest::SHA256.hexdigest(fixture_file('ci_secure_files/upload-keystore.jks'))
)
expect(json_response['id']).to eq(secure_file.id)
+ expect(Time.parse(json_response['created_at'])).to be_like_time(secure_file.created_at)
end
it 'creates a secure file with read_only permissions by default' do
@@ -163,7 +210,7 @@ RSpec.describe API::Ci::SecureFiles do
}
expect do
- post api("/projects/#{project.id}/secure_files", user), params: params
+ post api("/projects/#{project.id}/secure_files", maintainer), params: params
end.to change {project.secure_files.count}.by(1)
expect(json_response['permissions']).to eq('read_only')
@@ -176,11 +223,11 @@ RSpec.describe API::Ci::SecureFiles do
permissions: 'read_write'
}
- post api("/projects/#{project.id}/secure_files", user), params: post_params
+ post api("/projects/#{project.id}/secure_files", maintainer), params: post_params
secure_file_id = json_response['id']
- get api("/projects/#{project.id}/secure_files/#{secure_file_id}/download", user)
+ get api("/projects/#{project.id}/secure_files/#{secure_file_id}/download", maintainer)
expect(Base64.encode64(response.body)).to eq(Base64.encode64(fixture_file_upload('spec/fixtures/ci_secure_files/upload-keystore.jks').read))
end
@@ -188,7 +235,9 @@ RSpec.describe API::Ci::SecureFiles do
it 'returns an error when the file checksum fails to validate' do
secure_file.update!(checksum: 'foo')
- get api("/projects/#{project.id}/secure_files/#{secure_file.id}/download", user)
+ expect do
+ get api("/projects/#{project.id}/secure_files/#{secure_file.id}/download", maintainer)
+ end.not_to change { project.secure_files.count }
expect(response.code).to eq("500")
end
@@ -198,7 +247,9 @@ RSpec.describe API::Ci::SecureFiles do
name: 'upload-keystore.jks'
}
- post api("/projects/#{project.id}/secure_files", user), params: post_params
+ expect do
+ post api("/projects/#{project.id}/secure_files", maintainer), params: post_params
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('file is missing')
@@ -209,7 +260,9 @@ RSpec.describe API::Ci::SecureFiles do
file: fixture_file_upload('spec/fixtures/ci_secure_files/upload-keystore.jks')
}
- post api("/projects/#{project.id}/secure_files", user), params: post_params
+ expect do
+ post api("/projects/#{project.id}/secure_files", maintainer), params: post_params
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('name is missing')
@@ -222,7 +275,9 @@ RSpec.describe API::Ci::SecureFiles do
permissions: 'foo'
}
- post api("/projects/#{project.id}/secure_files", user), params: post_params
+ expect do
+ post api("/projects/#{project.id}/secure_files", maintainer), params: post_params
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('permissions does not have a valid value')
@@ -240,7 +295,9 @@ RSpec.describe API::Ci::SecureFiles do
name: 'upload-keystore.jks'
}
- post api("/projects/#{project.id}/secure_files", user), params: post_params
+ expect do
+ post api("/projects/#{project.id}/secure_files", maintainer), params: post_params
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -255,23 +312,39 @@ RSpec.describe API::Ci::SecureFiles do
name: 'upload-keystore.jks'
}
- post api("/projects/#{project.id}/secure_files", user), params: post_params
+ expect do
+ post api("/projects/#{project.id}/secure_files", maintainer), params: post_params
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:payload_too_large)
end
end
- context 'authorized user with invalid permissions' do
+ context 'authenticated user with read permissions' do
it 'does not create a secure file' do
- post api("/projects/#{project.id}/secure_files", user2)
+ expect do
+ post api("/projects/#{project.id}/secure_files", developer)
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
- context 'unauthorized user' do
+ context 'authenticated user with no permissions' do
it 'does not create a secure file' do
- post api("/projects/#{project.id}/secure_files")
+ expect do
+ post api("/projects/#{project.id}/secure_files", anonymous)
+ end.not_to change { project.secure_files.count }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not create a secure file' do
+ expect do
+ post api("/projects/#{project.id}/secure_files")
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -279,33 +352,49 @@ RSpec.describe API::Ci::SecureFiles do
end
describe 'DELETE /projects/:id/secure_files/:secure_file_id' do
- context 'authorized user with proper permissions' do
+ context 'authenticated user with admin permissions' do
it 'deletes the secure file' do
expect do
- delete api("/projects/#{project.id}/secure_files/#{secure_file.id}", user)
+ delete api("/projects/#{project.id}/secure_files/#{secure_file.id}", maintainer)
expect(response).to have_gitlab_http_status(:no_content)
- end.to change {project.secure_files.count}.by(-1)
+ end.to change { project.secure_files.count }
end
it 'responds with 404 Not Found if requesting non-existing secure_file' do
- delete api("/projects/#{project.id}/secure_files/99999", user)
+ expect do
+ delete api("/projects/#{project.id}/secure_files/#{non_existing_record_id}", maintainer)
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:not_found)
end
end
- context 'authorized user with invalid permissions' do
+ context 'authenticated user with read permissions' do
it 'does not delete the secure_file' do
- delete api("/projects/#{project.id}/secure_files/#{secure_file.id}", user2)
+ expect do
+ delete api("/projects/#{project.id}/secure_files/#{secure_file.id}", developer)
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
- context 'unauthorized user' do
+ context 'authenticated user with no permissions' do
it 'does not delete the secure_file' do
- delete api("/projects/#{project.id}/secure_files/#{secure_file.id}")
+ expect do
+ delete api("/projects/#{project.id}/secure_files/#{secure_file.id}", anonymous)
+ end.not_to change { project.secure_files.count }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not delete the secure_file' do
+ expect do
+ delete api("/projects/#{project.id}/secure_files/#{secure_file.id}")
+ end.not_to change { project.secure_files.count }
expect(response).to have_gitlab_http_status(:unauthorized)
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 156a4cf5ff3..67c2ec91540 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -127,6 +127,15 @@ RSpec.describe API::Commits do
it_behaves_like 'project commits'
end
+ context 'when repository does not exist' do
+ let(:project) { create(:project, creator: user, path: 'my.project') }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ let(:message) { '404 Repository Not Found' }
+ end
+ end
+
context "path optional parameter" do
it "returns project commits matching provided path parameter" do
path = 'files/ruby/popen.rb'
diff --git a/spec/requests/api/container_repositories_spec.rb b/spec/requests/api/container_repositories_spec.rb
index 9809702467d..90f0243dbfc 100644
--- a/spec/requests/api/container_repositories_spec.rb
+++ b/spec/requests/api/container_repositories_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe API::ContainerRepositories do
+ include_context 'container registry client stubs'
+
let_it_be(:project) { create(:project, :private) }
let_it_be(:reporter) { create(:user) }
let_it_be(:guest) { create(:user) }
@@ -103,6 +105,68 @@ RSpec.describe API::ContainerRepositories do
expect(json_response['tags_count']).to eq(2)
end
end
+
+ context 'with size param' do
+ let(:url) { "/registry/repositories/#{repository.id}?size=true" }
+ let(:on_com) { true }
+ let(:created_at) { ::ContainerRepository::MIGRATION_PHASE_1_STARTED_AT + 3.months }
+
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(on_com)
+ repository.update_column(:created_at, created_at)
+ end
+
+ it 'returns a repository and its size' do
+ stub_container_registry_gitlab_api_support(supported: true) do |client|
+ stub_container_registry_gitlab_api_repository_details(client, path: repository.path, size_bytes: 12345)
+ end
+
+ subject
+
+ expect(json_response['size']).to eq(12345)
+ end
+
+ context 'with a network error' do
+ it 'returns an error message' do
+ stub_container_registry_gitlab_api_network_error
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:service_unavailable)
+ expect(json_response['message']).to include('We are having trouble connecting to the Container Registry')
+ end
+ end
+
+ context 'with not supporting the gitlab api' do
+ it 'returns nil' do
+ stub_container_registry_gitlab_api_support(supported: false)
+
+ subject
+
+ expect(json_response['size']).to eq(nil)
+ end
+ end
+
+ context 'not on .com' do
+ let(:on_com) { false }
+
+ it 'returns nil' do
+ subject
+
+ expect(json_response['size']).to eq(nil)
+ end
+ end
+
+ context 'with an older container repository' do
+ let(:created_at) { ::ContainerRepository::MIGRATION_PHASE_1_STARTED_AT - 3.months }
+
+ it 'returns nil' do
+ subject
+
+ expect(json_response['size']).to eq(nil)
+ end
+ end
+ end
end
context 'with invalid repository id' do
diff --git a/spec/requests/api/deploy_tokens_spec.rb b/spec/requests/api/deploy_tokens_spec.rb
index 38380fa4460..b5f8da1f327 100644
--- a/spec/requests/api/deploy_tokens_spec.rb
+++ b/spec/requests/api/deploy_tokens_spec.rb
@@ -130,6 +130,55 @@ RSpec.describe API::DeployTokens do
end
end
+ describe 'GET /projects/:id/deploy_tokens/:token_id' do
+ subject do
+ get api("/projects/#{project.id}/deploy_tokens/#{deploy_token.id}", user)
+ response
+ end
+
+ context 'when unauthenticated' do
+ let(:user) { nil }
+
+ it { is_expected.to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'when authenticated as non-admin user' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.to have_gitlab_http_status(:forbidden) }
+ end
+
+ context 'when authenticated as maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it { is_expected.to have_gitlab_http_status(:ok) }
+
+ it 'returns specific deploy token for the project' do
+ subject
+
+ expect(response).to match_response_schema('public_api/v4/deploy_token')
+ end
+
+ context 'invalid request' do
+ it 'returns not found with invalid project id' do
+ get api("/projects/bad_id/deploy_tokens/#{deploy_token.id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with invalid token id' do
+ get api("/projects/#{project.id}/deploy_tokens/#{non_existing_record_id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
describe 'GET /groups/:id/deploy_tokens' do
subject do
get api("/groups/#{group.id}/deploy_tokens", user)
@@ -188,6 +237,55 @@ RSpec.describe API::DeployTokens do
end
end
+ describe 'GET /groups/:id/deploy_tokens/:token_id' do
+ subject do
+ get api("/groups/#{group.id}/deploy_tokens/#{group_deploy_token.id}", user)
+ response
+ end
+
+ context 'when unauthenticated' do
+ let(:user) { nil }
+
+ it { is_expected.to have_gitlab_http_status(:forbidden) }
+ end
+
+ context 'when authenticated as non-admin user' do
+ before do
+ group.add_developer(user)
+ end
+
+ it { is_expected.to have_gitlab_http_status(:forbidden) }
+ end
+
+ context 'when authenticated as maintainer' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it { is_expected.to have_gitlab_http_status(:ok) }
+
+ it 'returns specific deploy token for the group' do
+ subject
+
+ expect(response).to match_response_schema('public_api/v4/deploy_token')
+ end
+
+ context 'invalid request' do
+ it 'returns not found with invalid group id' do
+ get api("/groups/bad_id/deploy_tokens/#{group_deploy_token.id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns not found with invalid token id' do
+ get api("/groups/#{group.id}/deploy_tokens/#{non_existing_record_id}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
describe 'DELETE /projects/:id/deploy_tokens/:token_id' do
subject do
delete api("/projects/#{project.id}/deploy_tokens/#{deploy_token.id}", user)
@@ -232,10 +330,10 @@ RSpec.describe API::DeployTokens do
it 'returns bad_request with invalid token id' do
expect(::Projects::DeployTokens::DestroyService).to receive(:new)
- .with(project, user, token_id: 999)
+ .with(project, user, token_id: non_existing_record_id)
.and_raise(ActiveRecord::RecordNotFound)
- delete api("/projects/#{project.id}/deploy_tokens/999", user)
+ delete api("/projects/#{project.id}/deploy_tokens/#{non_existing_record_id}", user)
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -395,10 +493,10 @@ RSpec.describe API::DeployTokens do
it 'returns not found with invalid deploy token id' do
expect(::Groups::DeployTokens::DestroyService).to receive(:new)
- .with(group, user, token_id: 999)
+ .with(group, user, token_id: non_existing_record_id)
.and_raise(ActiveRecord::RecordNotFound)
- delete api("/groups/#{group.id}/deploy_tokens/999", user)
+ delete api("/groups/#{group.id}/deploy_tokens/#{non_existing_record_id}", user)
expect(response).to have_gitlab_http_status(:not_found)
end
diff --git a/spec/requests/api/error_tracking/collector_spec.rb b/spec/requests/api/error_tracking/collector_spec.rb
index 573da862b57..fa0b238dcad 100644
--- a/spec/requests/api/error_tracking/collector_spec.rb
+++ b/spec/requests/api/error_tracking/collector_spec.rb
@@ -26,7 +26,6 @@ RSpec.describe API::ErrorTracking::Collector do
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
@@ -42,6 +41,14 @@ RSpec.describe API::ErrorTracking::Collector do
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)
@@ -171,6 +178,12 @@ RSpec.describe API::ErrorTracking::Collector do
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) { {} }
diff --git a/spec/requests/api/error_tracking/project_settings_spec.rb b/spec/requests/api/error_tracking/project_settings_spec.rb
index 161e4f01ea5..c0c0680ef31 100644
--- a/spec/requests/api/error_tracking/project_settings_spec.rb
+++ b/spec/requests/api/error_tracking/project_settings_spec.rb
@@ -23,6 +23,21 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
end
end
+ shared_examples 'returns project settings with false for integrated' do
+ specify do
+ make_request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to eq(
+ 'active' => setting.reload.enabled,
+ 'project_name' => setting.project_name,
+ 'sentry_external_url' => setting.sentry_external_url,
+ 'api_url' => setting.api_url,
+ 'integrated' => false
+ )
+ end
+ end
+
shared_examples 'returns 404' do
it 'returns no project settings' do
make_request
@@ -46,7 +61,17 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
end
context 'patch settings' do
- it_behaves_like 'returns project settings'
+ context 'integrated_error_tracking feature enabled' do
+ it_behaves_like 'returns project settings'
+ end
+
+ context 'integrated_error_tracking feature disabled' do
+ before do
+ stub_feature_flags(integrated_error_tracking: false)
+ end
+
+ it_behaves_like 'returns project settings with false for integrated'
+ end
it 'updates enabled flag' do
expect(setting).to be_enabled
@@ -84,13 +109,19 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
context 'with integrated param' do
let(:params) { { active: true, integrated: true } }
- it 'updates the integrated flag' do
- expect(setting.integrated).to be_falsey
+ context 'integrated_error_tracking feature enabled' do
+ before do
+ stub_feature_flags(integrated_error_tracking: true)
+ end
- make_request
+ it 'updates the integrated flag' do
+ expect(setting.integrated).to be_falsey
+
+ make_request
- expect(json_response).to include('integrated' => true)
- expect(setting.reload.integrated).to be_truthy
+ expect(json_response).to include('integrated' => true)
+ expect(setting.reload.integrated).to be_truthy
+ end
end
end
end
@@ -170,7 +201,21 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
end
context 'get settings' do
- it_behaves_like 'returns project settings'
+ context 'integrated_error_tracking feature enabled' do
+ before do
+ stub_feature_flags(integrated_error_tracking: true)
+ end
+
+ it_behaves_like 'returns project settings'
+ end
+
+ context 'integrated_error_tracking feature disabled' do
+ before do
+ stub_feature_flags(integrated_error_tracking: false)
+ end
+
+ it_behaves_like 'returns project settings with false for integrated'
+ end
end
end
diff --git a/spec/requests/api/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb
index e1d8a9f0229..3a5c6103781 100644
--- a/spec/requests/api/generic_packages_spec.rb
+++ b/spec/requests/api/generic_packages_spec.rb
@@ -170,17 +170,6 @@ RSpec.describe API::GenericPackages do
end
end
- context 'generic_packages feature flag is disabled' do
- it 'responds with 404 Not Found' do
- stub_feature_flags(generic_packages: false)
- project.add_developer(user)
-
- authorize_upload_file(workhorse_headers.merge(personal_access_token_header))
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
def authorize_upload_file(request_headers, package_name: 'mypackage', file_name: 'myfile.tar.gz')
url = "/projects/#{project.id}/packages/generic/#{package_name}/0.0.1/#{file_name}/authorize"
diff --git a/spec/requests/api/graphql/ci/pipelines_spec.rb b/spec/requests/api/graphql/ci/pipelines_spec.rb
index 5ae68be46a2..741af676b6d 100644
--- a/spec/requests/api/graphql/ci/pipelines_spec.rb
+++ b/spec/requests/api/graphql/ci/pipelines_spec.rb
@@ -528,4 +528,37 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
end.not_to exceed_query_limit(control_count)
end
end
+
+ describe 'filtering' do
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ pipelines(updatedAfter: "#{updated_after_arg}", updatedBefore: "#{updated_before_arg}") {
+ nodes {
+ id
+ }}}}
+ )
+ end
+
+ context 'when filtered by updated_at' do
+ let_it_be(:oldish_pipeline) { create(:ci_empty_pipeline, project: project, updated_at: 3.days.ago) }
+ let_it_be(:older_pipeline) { create(:ci_empty_pipeline, project: project, updated_at: 10.days.ago) }
+
+ let(:updated_after_arg) { 5.days.ago }
+ let(:updated_before_arg) { 1.day.ago }
+
+ before do
+ post_graphql(query, current_user: user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'accepts filter params' do
+ pipeline_ids = graphql_data.dig('project', 'pipelines', 'nodes').map { |pipeline| pipeline.fetch('id') }
+
+ expect(pipeline_ids).to match_array(oldish_pipeline.to_global_id.to_s)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index fa16b9e1ddd..b99a3d14fb9 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -196,39 +196,6 @@ RSpec.describe 'Query.runner(id)' do
it_behaves_like 'runner details fetch', :inactive_instance_runner
end
- describe 'for runner inside group request' do
- let(:query) do
- %(
- query {
- group(fullPath: "#{group.full_path}") {
- runners {
- edges {
- webUrl
- node {
- id
- }
- }
- }
- }
- }
- )
- end
-
- it 'retrieves webUrl field with expected value' do
- post_graphql(query, current_user: user)
-
- runner_data = graphql_data_at(:group, :runners, :edges)
- expect(runner_data).to match_array [
- a_hash_including(
- 'webUrl' => "http://localhost/groups/#{group.full_path}/-/runners/#{active_group_runner.id}",
- 'node' => {
- 'id' => active_group_runner.to_global_id.to_s
- }
- )
- ]
- end
- end
-
describe 'for group runner request' do
let(:query) do
%(
diff --git a/spec/requests/api/graphql/ci/runner_web_url_edge_spec.rb b/spec/requests/api/graphql/ci/runner_web_url_edge_spec.rb
new file mode 100644
index 00000000000..767e958ea82
--- /dev/null
+++ b/spec/requests/api/graphql/ci/runner_web_url_edge_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe 'RunnerWebUrlEdge' do
+ include GraphqlHelpers
+
+ describe 'inside a Query.group' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) }
+
+ let(:edges_graphql_data) { graphql_data.dig('group', 'runners', 'edges') }
+
+ let(:query) do
+ <<~GQL
+ query($path: ID!) {
+ group(fullPath: $path) {
+ runners {
+ edges {
+ editUrl
+ webUrl
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ before do
+ post_graphql(query, current_user: user, variables: { path: group.full_path })
+ end
+
+ context 'with an authorized user' do
+ let(:user) { create_default(:user, :admin) }
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns correct URLs' do
+ expect(edges_graphql_data).to match_array [
+ {
+ 'editUrl' => Gitlab::Routing.url_helpers.edit_group_runner_url(group, group_runner),
+ 'webUrl' => Gitlab::Routing.url_helpers.group_runner_url(group, group_runner)
+ }
+ ]
+ end
+ end
+
+ context 'with an unauthorized user' do
+ let(:user) { create(:user) }
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns no edges' do
+ expect(edges_graphql_data).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
index 35a70a180a2..922a9ab277e 100644
--- a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
+++ b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
@@ -3,17 +3,19 @@ require 'spec_helper'
RSpec.describe 'container repository details' do
include_context 'container registry tags'
+ include_context 'container registry client stubs'
+
using RSpec::Parameterized::TableSyntax
include GraphqlHelpers
let_it_be_with_reload(:project) { create(:project) }
- let_it_be(:container_repository) { create(:container_repository, project: project) }
+ let_it_be_with_reload(:container_repository) { create(:container_repository, project: project) }
let(:query) do
graphql_query_for(
'containerRepository',
{ id: container_repository_global_id },
- all_graphql_fields_for('ContainerRepositoryDetails', excluded: ['pipeline'])
+ all_graphql_fields_for('ContainerRepositoryDetails', excluded: %w[pipeline size])
)
end
@@ -220,6 +222,80 @@ RSpec.describe 'container repository details' do
end
end
+ context 'size field' do
+ let(:size_response) { container_repository_details_response.dig('size') }
+ let(:on_com) { true }
+ let(:created_at) { ::ContainerRepository::MIGRATION_PHASE_1_STARTED_AT + 3.months }
+ let(:variables) do
+ { id: container_repository_global_id }
+ end
+
+ let(:query) do
+ <<~GQL
+ query($id: ID!) {
+ containerRepository(id: $id) {
+ size
+ }
+ }
+ GQL
+ end
+
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(on_com)
+ container_repository.update_column(:created_at, created_at)
+ end
+
+ it 'returns the size' do
+ stub_container_registry_gitlab_api_support(supported: true) do |client|
+ stub_container_registry_gitlab_api_repository_details(client, path: container_repository.path, size_bytes: 12345)
+ end
+
+ subject
+
+ expect(size_response).to eq(12345)
+ end
+
+ context 'with a network error' do
+ it 'returns an error' do
+ stub_container_registry_gitlab_api_network_error
+
+ subject
+
+ expect_graphql_errors_to_include("Can't connect to the Container Registry. If this error persists, please review the troubleshooting documentation.")
+ end
+ end
+
+ context 'with not supporting the gitlab api' do
+ it 'returns nil' do
+ stub_container_registry_gitlab_api_support(supported: false)
+
+ subject
+
+ expect(size_response).to eq(nil)
+ end
+ end
+
+ context 'not on .com' do
+ let(:on_com) { false }
+
+ it 'returns nil' do
+ subject
+
+ expect(size_response).to eq(nil)
+ end
+ end
+
+ context 'with an older container repository' do
+ let(:created_at) { ::ContainerRepository::MIGRATION_PHASE_1_STARTED_AT - 3.months }
+
+ it 'returns nil' do
+ subject
+
+ expect(size_response).to eq(nil)
+ end
+ end
+ end
+
context 'with tags with a manifest containing nil fields' do
let(:tags_response) { container_repository_details_response.dig('tags', 'nodes') }
let(:errors) { container_repository_details_response.dig('errors') }
diff --git a/spec/requests/api/graphql/group/group_members_spec.rb b/spec/requests/api/graphql/group/group_members_spec.rb
index 06afb5b9a49..78852622835 100644
--- a/spec/requests/api/graphql/group/group_members_spec.rb
+++ b/spec/requests/api/graphql/group/group_members_spec.rb
@@ -53,6 +53,30 @@ RSpec.describe 'getting group members information' do
end
end
+ context "when requesting member's notification email" do
+ context 'when current_user is admin' do
+ let_it_be(:admin_user) { create(:user, :admin) }
+
+ it 'returns notification email' do
+ fetch_members_notification_email(current_user: admin_user)
+ notification_emails = graphql_data_at(:group, :group_members, :edges, :node, :notification_email)
+
+ expect(notification_emails).to all be_present
+ expect(graphql_errors).to be_nil
+ end
+ end
+
+ context 'when current_user is not admin' do
+ it 'returns an error' do
+ fetch_members_notification_email
+
+ expect(graphql_errors.first)
+ .to include('path' => ['group', 'groupMembers', 'edges', 0, 'node', 'notificationEmail'],
+ 'message' => a_string_including("you don't have permission to perform this action"))
+ end
+ end
+ end
+
context 'member relations' do
let_it_be(:child_group) { create(:group, :public, parent: parent_group) }
let_it_be(:grandchild_group) { create(:group, :public, parent: child_group) }
@@ -117,6 +141,10 @@ RSpec.describe 'getting group members information' do
post_graphql(members_query(group.full_path, args), current_user: current_user)
end
+ def fetch_members_notification_email(group: parent_group, current_user: user)
+ post_graphql(member_notification_email_query(group.full_path), current_user: current_user)
+ end
+
def members_query(group_path, args = {})
members_node = <<~NODE
edges {
@@ -134,6 +162,24 @@ RSpec.describe 'getting group members information' do
)
end
+ def member_notification_email_query(group_path)
+ members_node = <<~NODE
+ edges {
+ node {
+ user {
+ id
+ }
+ notificationEmail
+ }
+ }
+ NODE
+
+ graphql_query_for("group",
+ { full_path: group_path },
+ [query_graphql_field("groupMembers", {}, members_node)]
+ )
+ end
+
def expect_array_response(*items)
expect(response).to have_gitlab_http_status(:success)
member_gids = graphql_data_at(:group, :group_members, :edges, :node, :user, :id)
diff --git a/spec/requests/api/graphql/group/issues_spec.rb b/spec/requests/api/graphql/group/issues_spec.rb
index 332bf242e9c..26338f46611 100644
--- a/spec/requests/api/graphql/group/issues_spec.rb
+++ b/spec/requests/api/graphql/group/issues_spec.rb
@@ -44,6 +44,31 @@ RSpec.describe 'getting an issue list for a group' do
end
end
+ context 'when there are archived projects' do
+ let_it_be(:archived_project) { create(:project, :archived, group: group1) }
+ let_it_be(:archived_issue) { create(:issue, project: archived_project) }
+
+ before_all do
+ group1.add_developer(current_user)
+ end
+
+ it 'excludes issues from archived projects by default' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid)
+ end
+
+ context 'when include_archived is true' do
+ let(:issue_filter_params) { { include_archived: true } }
+
+ it 'includes issues from archived projects' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid, archived_issue.to_global_id.to_s)
+ end
+ end
+ end
+
context 'when there is a confidential issue' do
let_it_be(:confidential_issue1) { create(:issue, :confidential, project: project1) }
let_it_be(:confidential_issue2) { create(:issue, :confidential, project: project2) }
diff --git a/spec/requests/api/graphql/group/merge_requests_spec.rb b/spec/requests/api/graphql/group/merge_requests_spec.rb
index e9a5e558b1d..c0faff11c8d 100644
--- a/spec/requests/api/graphql/group/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/group/merge_requests_spec.rb
@@ -16,6 +16,9 @@ RSpec.describe 'Query.group.mergeRequests' do
let_it_be(:project_x) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project_x]) }
+ let_it_be(:archived_project) { create(:project, :archived, :repository, group: group) }
+ let_it_be(:archived_mr) { create(:merge_request, source_project: archived_project) }
+
let_it_be(:mr_attrs) do
{ target_branch: 'master' }
end
@@ -119,4 +122,22 @@ RSpec.describe 'Query.group.mergeRequests' do
expect(mrs_data).to match_array(expected_mrs(mrs_a + mrs_b + mrs_c))
end
end
+
+ describe 'passing include_archived: true' do
+ let(:query) do
+ <<~GQL
+ query($path: ID!) {
+ group(fullPath: $path) {
+ mergeRequests(includeArchived: true) { nodes { id } }
+ }
+ }
+ GQL
+ end
+
+ it 'can find all merge requests in the group, including from archived projects' do
+ post_graphql(query, current_user: user, variables: { path: group.full_path })
+
+ expect(mrs_data).to match_array(expected_mrs(mrs_a + mrs_b + [archived_mr]))
+ end
+ end
end
diff --git a/spec/requests/api/graphql/group/work_item_types_spec.rb b/spec/requests/api/graphql/group/work_item_types_spec.rb
index 0667e09d1e9..a33e3ae5427 100644
--- a/spec/requests/api/graphql/group/work_item_types_spec.rb
+++ b/spec/requests/api/graphql/group/work_item_types_spec.rb
@@ -64,8 +64,8 @@ RSpec.describe 'getting a list of work item types for a group' do
post_graphql(query, current_user: current_user)
end
- it 'makes the workItemTypes field unavailable' do
- expect(graphql_errors).to contain_exactly(hash_including("message" => "Field 'workItemTypes' doesn't exist on type 'Group'"))
+ it 'returns null' do
+ expect(graphql_data.dig('group', 'workItemTypes')).to be_nil
end
end
end
diff --git a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
index 79d687a2bdb..02b79dac489 100644
--- a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
@@ -9,12 +9,10 @@ RSpec.describe 'Setting issues crm contacts' do
let_it_be(:group) { create(:group, :crm_enabled) }
let_it_be(:subgroup) { create(:group, :crm_enabled, parent: group) }
let_it_be(:project) { create(:project, group: subgroup) }
- let_it_be(:group_contacts) { create_list(:contact, 4, group: group) }
- let_it_be(:subgroup_contacts) { create_list(:contact, 4, group: subgroup) }
+ let_it_be(:contacts) { create_list(:contact, 4, group: group) }
let(:issue) { create(:issue, project: project) }
let(:operation_mode) { Types::MutationOperationModeEnum.default_mode }
- let(:contacts) { subgroup_contacts }
let(:initial_contacts) { contacts[0..1] }
let(:mutation_contacts) { contacts[1..2] }
let(:contact_ids) { contact_global_ids(mutation_contacts) }
@@ -116,15 +114,7 @@ RSpec.describe 'Setting issues crm contacts' do
end
end
- context 'with issue group contacts' do
- let(:contacts) { subgroup_contacts }
-
- it_behaves_like 'successful mutation'
- end
-
- context 'with issue ancestor group contacts' do
- it_behaves_like 'successful mutation'
- end
+ it_behaves_like 'successful mutation'
context 'when the contact does not exist' do
let(:contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
index 87c752393ea..2bc671e4ca5 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -8,13 +8,16 @@ RSpec.describe 'Adding a Note' do
let_it_be(:current_user) { create(:user) }
let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, :repository) }
let(:discussion) { nil }
+ let(:head_sha) { nil }
+ let(:body) { 'Body text' }
let(:mutation) do
variables = {
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
discussion_id: (GitlabSchema.id_from_object(discussion).to_s if discussion),
- body: 'Body text',
+ merge_request_diff_head_sha: head_sha.presence,
+ body: body,
confidential: true
}
@@ -54,7 +57,7 @@ RSpec.describe 'Adding a Note' do
let(:discussion) { create(:discussion_note).to_discussion }
it_behaves_like 'a mutation that returns top-level errors',
- errors: ["The discussion does not exist or you don't have permission to perform this action"]
+ errors: ["The discussion does not exist or you don't have permission to perform this action"]
end
context 'when the user has permission to create notes on the discussion' do
@@ -75,5 +78,29 @@ RSpec.describe 'Adding a Note' do
end
end
end
+
+ context 'when body only contains quick actions' do
+ let(:head_sha) { noteable.diff_head_sha }
+ let(:body) { '/merge' }
+
+ before do
+ project.add_developer(current_user)
+ end
+
+ # NOTE: Known issue https://gitlab.com/gitlab-org/gitlab/-/issues/346557
+ it 'returns a nil note and info about the command in errors' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to include(
+ 'errors' => [/Merged this merge request/],
+ 'note' => nil
+ )
+ end
+
+ it 'starts the merge process' do
+ expect { post_graphql_mutation(mutation, current_user: current_user) }
+ .to change { noteable.reload.merge_jid.present? }.from(false).to(true)
+ end
+ end
end
end
diff --git a/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb
new file mode 100644
index 00000000000..8d33f8e1806
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe "Create a work item from a task in a work item's description" do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
+ let_it_be(:work_item, refind: true) { create(:work_item, project: project, description: '- [ ] A task in a list', lock_version: 3) }
+
+ let(:lock_version) { work_item.lock_version }
+ let(:input) do
+ {
+ 'id' => work_item.to_global_id.to_s,
+ 'workItemData' => {
+ 'title' => 'A task in a list',
+ 'workItemTypeId' => WorkItems::Type.default_by_type(:task).to_global_id.to_s,
+ 'lineNumberStart' => 1,
+ 'lineNumberEnd' => 1,
+ 'lockVersion' => lock_version
+ }
+ }
+ end
+
+ let(:mutation) { graphql_mutation(:workItemCreateFromTask, input) }
+ let(:mutation_response) { graphql_mutation_response(:work_item_create_from_task) }
+
+ context 'the user is not allowed to update a work item' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user has permissions to create a work item' do
+ let(:current_user) { developer }
+
+ it 'creates the work item' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change(WorkItem, :count).by(1)
+
+ created_work_item = WorkItem.last
+ work_item.reload
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(work_item.description).to eq("- [ ] #{created_work_item.to_reference}+")
+ expect(created_work_item.issue_type).to eq('task')
+ expect(created_work_item.work_item_type.base_type).to eq('task')
+ expect(mutation_response['workItem']).to include('id' => work_item.to_global_id.to_s)
+ expect(mutation_response['newWorkItem']).to include('id' => created_work_item.to_global_id.to_s)
+ end
+
+ context 'when creating a work item fails' do
+ let(:lock_version) { 2 }
+
+ it 'makes no changes to the DB and returns an error message' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to not_change(WorkItem, :count).and(
+ not_change(work_item, :description)
+ )
+
+ expect(mutation_response['errors']).to contain_exactly('Stale work item. Check lock version')
+ end
+ end
+
+ it_behaves_like 'has spam protection' do
+ let(:mutation_class) { ::Mutations::WorkItems::CreateFromTask }
+ end
+
+ context 'when the work_items feature flag is disabled' do
+ before do
+ stub_feature_flags(work_items: false)
+ end
+
+ it 'does nothing and returns and error' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to not_change(WorkItem, :count)
+
+ expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project')
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/namespace_query_spec.rb b/spec/requests/api/graphql/namespace_query_spec.rb
index f7ee2bcb55d..e17469901c6 100644
--- a/spec/requests/api/graphql/namespace_query_spec.rb
+++ b/spec/requests/api/graphql/namespace_query_spec.rb
@@ -31,7 +31,8 @@ RSpec.describe 'Query' do
it 'fetches the expected data' do
expect(query_result).to include(
'fullPath' => target_namespace.full_path,
- 'name' => target_namespace.name
+ 'name' => target_namespace.name,
+ 'crossProjectPipelineAvailable' => target_namespace.licensed_feature_available?(:cross_project_pipeline)
)
end
end
diff --git a/spec/requests/api/graphql/project/jira_service_spec.rb b/spec/requests/api/graphql/project/jira_service_spec.rb
index 64e9e04ae44..d6abe94b873 100644
--- a/spec/requests/api/graphql/project/jira_service_spec.rb
+++ b/spec/requests/api/graphql/project/jira_service_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe 'query Jira service' do
services(active: true, type: JIRA_SERVICE) {
nodes {
type
+ serviceType
}
}
}
@@ -23,7 +24,7 @@ RSpec.describe 'query Jira service' do
)
end
- let(:services) { graphql_data.dig('project', 'services', 'nodes')}
+ let(:services) { graphql_data.dig('project', 'services', 'nodes') }
it_behaves_like 'unauthorized users cannot read services'
@@ -35,10 +36,8 @@ RSpec.describe 'query Jira service' do
it_behaves_like 'a working graphql query'
- it 'retuns list of jira imports' do
- service = services.first
-
- expect(service['type']).to eq('JiraService')
+ it 'returns list of jira integrations' do
+ expect(services).to contain_exactly({ 'type' => 'JiraService', 'serviceType' => 'JIRA_SERVICE' })
end
end
end
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index 353bf0356f6..cefe88aafc8 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -76,6 +76,24 @@ RSpec.describe 'getting merge request information nested in a project' do
end
end
+ context 'when the merge_request has committers' do
+ let(:mr_fields) do
+ <<~SELECT
+ committers { nodes { id username } }
+ SELECT
+ end
+
+ it 'includes committers' do
+ expected = merge_request.committers.map do |r|
+ a_hash_including('id' => global_id_of(r), 'username' => r.username)
+ end
+
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data_at(:project, :merge_request, :committers, :nodes)).to match_array(expected)
+ end
+ end
+
describe 'diffStats' do
let(:mr_fields) do
<<~FIELDS
diff --git a/spec/requests/api/graphql/project/work_item_types_spec.rb b/spec/requests/api/graphql/project/work_item_types_spec.rb
index 2caaedda2a1..157961c3f66 100644
--- a/spec/requests/api/graphql/project/work_item_types_spec.rb
+++ b/spec/requests/api/graphql/project/work_item_types_spec.rb
@@ -64,8 +64,8 @@ RSpec.describe 'getting a list of work item types for a project' do
post_graphql(query, current_user: current_user)
end
- it 'makes the workItemTypes field unavailable' do
- expect(graphql_errors).to contain_exactly(hash_including("message" => "Field 'workItemTypes' doesn't exist on type 'Project'"))
+ it 'returns null' do
+ expect(graphql_data.dig('project', 'workItemTypes')).to be_nil
end
end
end
diff --git a/spec/requests/api/graphql/query_spec.rb b/spec/requests/api/graphql/query_spec.rb
index ecc7fffaef7..d650acc8354 100644
--- a/spec/requests/api/graphql/query_spec.rb
+++ b/spec/requests/api/graphql/query_spec.rb
@@ -11,6 +11,30 @@ RSpec.describe 'Query' do
let(:current_user) { developer }
+ describe 'gitpodEnabled field' do
+ let(:gitpod_enabled) { true }
+ let(:gitpod_enabled_query) do
+ <<~GRAPHQL
+ { gitpodEnabled }
+ GRAPHQL
+ end
+
+ before do
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:gitpod_enabled).and_return(gitpod_enabled)
+ post_graphql(gitpod_enabled_query)
+ end
+
+ context 'When Gitpod is enabled for the application' do
+ it { expect(graphql_data).to include('gitpodEnabled' => true) }
+ end
+
+ context 'When Gitpod is disabled for the application' do
+ let(:gitpod_enabled) { false }
+
+ it { expect(graphql_data).to include('gitpodEnabled' => false) }
+ end
+ end
+
describe '.designManagement' do
include DesignManagementTestHelpers
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
new file mode 100644
index 00000000000..bc5a8b3e006
--- /dev/null
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.work_item(id)' do
+ include GraphqlHelpers
+
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:project) { create(:project, :private).tap { |project| project.add_developer(developer) } }
+ let_it_be(:work_item) { create(:work_item, project: project) }
+
+ let(:current_user) { developer }
+ let(:work_item_data) { graphql_data['workItem'] }
+ let(:work_item_fields) { all_graphql_fields_for('WorkItem') }
+ let(:global_id) { work_item.to_gid.to_s }
+
+ let(:query) do
+ graphql_query_for('workItem', { 'id' => global_id }, work_item_fields)
+ end
+
+ context 'when the user can read the work item' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns all fields' do
+ expect(work_item_data).to include(
+ 'description' => work_item.description,
+ 'id' => work_item.to_gid.to_s,
+ 'iid' => work_item.iid.to_s,
+ 'lockVersion' => work_item.lock_version,
+ 'state' => "OPEN",
+ 'title' => work_item.title,
+ 'workItemType' => hash_including('id' => work_item.work_item_type.to_gid.to_s)
+ )
+ end
+
+ context 'when an Issue Global ID is provided' do
+ let(:global_id) { Issue.find(work_item.id).to_gid.to_s }
+
+ it 'allows an Issue GID as input' do
+ expect(work_item_data).to include('id' => work_item.to_gid.to_s)
+ end
+ end
+ end
+
+ context 'when the user can not read the work item' do
+ let(:current_user) { create(:user) }
+
+ before do
+ post_graphql(query)
+ end
+
+ it 'returns an access error' do
+ expect(work_item_data).to be_nil
+ expect(graphql_errors).to contain_exactly(
+ hash_including('message' => ::Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
+ )
+ end
+ end
+
+ context 'when the work_items feature flag is disabled' do
+ before do
+ stub_feature_flags(work_items: false)
+ end
+
+ it 'returns nil' do
+ post_graphql(query)
+
+ expect(work_item_data).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb
index c48b5007f91..8e127bf0710 100644
--- a/spec/requests/api/group_clusters_spec.rb
+++ b/spec/requests/api/group_clusters_spec.rb
@@ -22,6 +22,10 @@ RSpec.describe API::GroupClusters do
groups: [group])
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { get api("/groups/#{group.id}/clusters", current_user) }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
get api("/groups/#{group.id}/clusters", unauthorized_user)
@@ -66,6 +70,10 @@ RSpec.describe API::GroupClusters do
groups: [group])
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { get api("/groups/#{group.id}/clusters/#{cluster_id}", current_user) }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
get api("/groups/#{group.id}/clusters/#{cluster_id}", unauthorized_user)
@@ -181,6 +189,10 @@ RSpec.describe API::GroupClusters do
}
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
post api("/groups/#{group.id}/clusters/user", unauthorized_user), params: cluster_params
@@ -362,6 +374,10 @@ RSpec.describe API::GroupClusters do
groups: [group], domain: 'old-domain.com')
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { put api("/groups/#{group.id}/clusters/#{cluster.id}", current_user), params: update_params }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
put api("/groups/#{group.id}/clusters/#{cluster.id}", unauthorized_user), params: update_params
@@ -503,6 +519,10 @@ RSpec.describe API::GroupClusters do
groups: [group])
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { delete api("/groups/#{group.id}/clusters/#{cluster.id}", current_user), params: cluster_params }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
delete api("/groups/#{group.id}/clusters/#{cluster.id}", unauthorized_user), params: cluster_params
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
index 11738e3cba8..34533da53dd 100644
--- a/spec/requests/api/group_labels_spec.rb
+++ b/spec/requests/api/group_labels_spec.rb
@@ -140,7 +140,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(group_label1.name)
- expect(json_response['color']).to eq(group_label1.color)
+ expect(json_response['color']).to be_color(group_label1.color)
expect(json_response['description']).to eq(group_label1.description)
end
end
@@ -156,7 +156,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_new_label_title)
- expect(json_response['color']).to eq('#FFAABB')
+ expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to eq('test')
end
@@ -169,7 +169,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_new_label_title)
- expect(json_response['color']).to eq('#FFAABB')
+ expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to be_nil
end
@@ -276,7 +276,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(valid_new_label_title)
- expect(json_response['color']).to eq('#FFFFFF')
+ expect(json_response['color']).to be_color('#FFFFFF')
expect(json_response['description']).to eq('test')
end
@@ -332,7 +332,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(valid_new_label_title)
- expect(json_response['color']).to eq('#FFFFFF')
+ expect(json_response['color']).to be_color('#FFFFFF')
expect(json_response['description']).to eq('test')
end
diff --git a/spec/requests/api/integrations_spec.rb b/spec/requests/api/integrations_spec.rb
index 033c80a5696..220c58afbe9 100644
--- a/spec/requests/api/integrations_spec.rb
+++ b/spec/requests/api/integrations_spec.rb
@@ -10,6 +10,14 @@ RSpec.describe API::Integrations do
create(:project, creator_id: user.id, namespace: user.namespace)
end
+ # The API supports all integrations except the GitLab Slack Application
+ # integration; this integration must be installed via the UI.
+ def self.integration_names
+ names = Integration.available_integration_names
+ names.delete(Integrations::GitlabSlackApplication.to_param) if Gitlab.ee?
+ names
+ end
+
%w[integrations services].each do |endpoint|
describe "GET /projects/:id/#{endpoint}" do
it 'returns authentication error when unauthenticated' do
@@ -43,7 +51,7 @@ RSpec.describe API::Integrations do
end
end
- Integration.available_integration_names.each do |integration|
+ integration_names.each do |integration|
describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
include_context integration
diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb
index 59d185fe6c8..0e566dd8c0e 100644
--- a/spec/requests/api/internal/kubernetes_spec.rb
+++ b/spec/requests/api/internal/kubernetes_spec.rb
@@ -169,6 +169,7 @@ RSpec.describe API::Internal::Kubernetes do
'features' => {}
),
'gitaly_repository' => a_hash_including(
+ 'default_branch' => project.default_branch_or_main,
'storage_name' => project.repository_storage,
'relative_path' => project.disk_path + '.git',
'gl_repository' => "project-#{project.id}",
diff --git a/spec/requests/api/internal/mail_room_spec.rb b/spec/requests/api/internal/mail_room_spec.rb
index f3ca3708c0c..67ea617f90d 100644
--- a/spec/requests/api/internal/mail_room_spec.rb
+++ b/spec/requests/api/internal/mail_room_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe API::Internal::MailRoom do
}
end
- let(:auth_payload) { { 'iss' => Gitlab::MailRoom::Authenticator::INTERNAL_API_REQUEST_JWT_ISSUER, 'iat' => (Time.now - 10.seconds).to_i } }
+ let(:auth_payload) { { 'iss' => Gitlab::MailRoom::INTERNAL_API_REQUEST_JWT_ISSUER, 'iat' => (Time.now - 10.seconds).to_i } }
let(:incoming_email_secret) { 'incoming_email_secret' }
let(:service_desk_email_secret) { 'service_desk_email_secret' }
@@ -51,7 +51,7 @@ RSpec.describe API::Internal::MailRoom do
context 'handle incoming_email successfully' do
let(:auth_headers) do
jwt_token = JWT.encode(auth_payload, incoming_email_secret, 'HS256')
- { Gitlab::MailRoom::Authenticator::INTERNAL_API_REQUEST_HEADER => jwt_token }
+ { Gitlab::MailRoom::INTERNAL_API_REQUEST_HEADER => jwt_token }
end
it 'schedules a EmailReceiverWorker job with raw email content' do
@@ -71,7 +71,7 @@ RSpec.describe API::Internal::MailRoom do
context 'handle service_desk_email successfully' do
let(:auth_headers) do
jwt_token = JWT.encode(auth_payload, service_desk_email_secret, 'HS256')
- { Gitlab::MailRoom::Authenticator::INTERNAL_API_REQUEST_HEADER => jwt_token }
+ { Gitlab::MailRoom::INTERNAL_API_REQUEST_HEADER => jwt_token }
end
it 'schedules a ServiceDeskEmailReceiverWorker job with raw email content' do
@@ -91,7 +91,7 @@ RSpec.describe API::Internal::MailRoom do
context 'email content exceeds limit' do
let(:auth_headers) do
jwt_token = JWT.encode(auth_payload, incoming_email_secret, 'HS256')
- { Gitlab::MailRoom::Authenticator::INTERNAL_API_REQUEST_HEADER => jwt_token }
+ { Gitlab::MailRoom::INTERNAL_API_REQUEST_HEADER => jwt_token }
end
before do
@@ -134,7 +134,7 @@ RSpec.describe API::Internal::MailRoom do
context 'wrong token authentication' do
let(:auth_headers) do
jwt_token = JWT.encode(auth_payload, 'wrongsecret', 'HS256')
- { Gitlab::MailRoom::Authenticator::INTERNAL_API_REQUEST_HEADER => jwt_token }
+ { Gitlab::MailRoom::INTERNAL_API_REQUEST_HEADER => jwt_token }
end
it 'responds with 401 Unauthorized' do
@@ -147,7 +147,7 @@ RSpec.describe API::Internal::MailRoom do
context 'wrong mailbox type authentication' do
let(:auth_headers) do
jwt_token = JWT.encode(auth_payload, service_desk_email_secret, 'HS256')
- { Gitlab::MailRoom::Authenticator::INTERNAL_API_REQUEST_HEADER => jwt_token }
+ { Gitlab::MailRoom::INTERNAL_API_REQUEST_HEADER => jwt_token }
end
it 'responds with 401 Unauthorized' do
@@ -160,7 +160,7 @@ RSpec.describe API::Internal::MailRoom do
context 'not supported mailbox type' do
let(:auth_headers) do
jwt_token = JWT.encode(auth_payload, incoming_email_secret, 'HS256')
- { Gitlab::MailRoom::Authenticator::INTERNAL_API_REQUEST_HEADER => jwt_token }
+ { Gitlab::MailRoom::INTERNAL_API_REQUEST_HEADER => jwt_token }
end
it 'responds with 401 Unauthorized' do
@@ -181,7 +181,7 @@ RSpec.describe API::Internal::MailRoom do
let(:auth_headers) do
jwt_token = JWT.encode(auth_payload, service_desk_email_secret, 'HS256')
- { Gitlab::MailRoom::Authenticator::INTERNAL_API_REQUEST_HEADER => jwt_token }
+ { Gitlab::MailRoom::INTERNAL_API_REQUEST_HEADER => jwt_token }
end
it 'responds with 401 Unauthorized' do
diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb
index 702e6ef0a2a..741cf793a77 100644
--- a/spec/requests/api/invitations_spec.rb
+++ b/spec/requests/api/invitations_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe API::Invitations do
let(:email) { 'email1@example.com' }
let(:email2) { 'email2@example.com' }
- let_it_be(:project) do
+ let_it_be(:project, reload: true) do
create(:project, :public, creator_id: maintainer.id, namespace: maintainer.namespace) do |project|
project.add_developer(developer)
project.add_maintainer(maintainer)
@@ -208,6 +208,25 @@ RSpec.describe API::Invitations do
end
end
+ context 'when adding project bot' do
+ let_it_be(:project_bot) { create(:user, :project_bot) }
+
+ before do
+ unrelated_project = create(:project)
+ unrelated_project.add_maintainer(project_bot)
+ end
+
+ it 'returns error' do
+ expect do
+ post invitations_url(source, maintainer),
+ params: { email: project_bot.email, access_level: Member::DEVELOPER }
+
+ expect(json_response['status']).to eq 'error'
+ expect(json_response['message'][project_bot.email]).to include('User project bots cannot be added to other groups / projects')
+ end.not_to change { source.members.count }
+ end
+ end
+
it "returns a message if member already exists" do
post invitations_url(source, maintainer),
params: { email: developer.email, access_level: Member::MAINTAINER }
diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb
index 82692366589..7c8994ad9ba 100644
--- a/spec/requests/api/issues/post_projects_issues_spec.rb
+++ b/spec/requests/api/issues/post_projects_issues_spec.rb
@@ -447,7 +447,7 @@ RSpec.describe API::Issues do
post_issue
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to eq({ 'error' => 'Spam detected' })
+ expect(json_response['message']['base']).to match_array([/issue has been recognized as spam/])
end
it 'creates a new spam log entry' do
diff --git a/spec/requests/api/issues/put_projects_issues_spec.rb b/spec/requests/api/issues/put_projects_issues_spec.rb
index dac721cbea0..6ea77cc6578 100644
--- a/spec/requests/api/issues/put_projects_issues_spec.rb
+++ b/spec/requests/api/issues/put_projects_issues_spec.rb
@@ -199,8 +199,8 @@ RSpec.describe API::Issues do
expect(spam_service).to receive_messages(check_for_spam?: true)
end
- expect_next_instance_of(Spam::SpamVerdictService) do |verdict_service|
- expect(verdict_service).to receive(:execute).and_return(DISALLOW)
+ allow_next_instance_of(Spam::AkismetService) do |akismet_service|
+ allow(akismet_service).to receive(:spam?).and_return(true)
end
end
@@ -217,7 +217,7 @@ RSpec.describe API::Issues do
update_issue
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response).to include('message' => { 'error' => 'Spam detected' })
+ expect(json_response['message']['base']).to match_array([/issue has been recognized as spam/])
end
it 'creates a new spam log entry' do
@@ -323,44 +323,44 @@ RSpec.describe API::Issues do
end
it 'removes all labels and touches the record' do
- Timecop.travel(1.minute.from_now) do
+ travel_to(2.minutes.from_now) do
put api_for_user, params: { labels: '' }
end
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['labels']).to eq([])
- expect(json_response['updated_at']).to be > Time.now
+ expect(json_response['updated_at']).to be > Time.current
end
it 'removes all labels and touches the record with labels param as array' do
- Timecop.travel(1.minute.from_now) do
+ travel_to(2.minutes.from_now) do
put api_for_user, params: { labels: [''] }
end
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['labels']).to eq([])
- expect(json_response['updated_at']).to be > Time.now
+ expect(json_response['updated_at']).to be > Time.current
end
it 'updates labels and touches the record' do
- Timecop.travel(1.minute.from_now) do
+ travel_to(2.minutes.from_now) do
put api_for_user, params: { labels: 'foo,bar' }
end
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['labels']).to contain_exactly('foo', 'bar')
- expect(json_response['updated_at']).to be > Time.now
+ expect(json_response['updated_at']).to be > Time.current
end
it 'updates labels and touches the record with labels param as array' do
- Timecop.travel(1.minute.from_now) do
+ travel_to(2.minutes.from_now) do
put api_for_user, params: { labels: %w(foo bar) }
end
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['labels']).to include 'foo'
expect(json_response['labels']).to include 'bar'
- expect(json_response['updated_at']).to be > Time.now
+ expect(json_response['updated_at']).to be > Time.current
end
it 'allows special label names' do
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index db9d72245b3..48f2c45bd98 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(valid_label_title_2)
- expect(json_response['color']).to eq(label1.color)
+ expect(json_response['color']).to be_color(label1.color)
end
it "returns 200 if colors is changed (#{route_type} route)" do
@@ -42,7 +42,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(label1.name)
- expect(json_response['color']).to eq('#FFFFFF')
+ expect(json_response['color']).to be_color('#FFFFFF')
end
it "returns 200 if a priority is added (#{route_type} route)" do
@@ -86,7 +86,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(valid_label_title_2)
- expect(json_response['color']).to eq('#FFFFFF')
+ expect(json_response['color']).to be_color('#FFFFFF')
expect(json_response['description']).to eq('test')
end
@@ -266,8 +266,8 @@ RSpec.describe API::Labels do
'open_merge_requests_count' => 0,
'name' => group_label.name,
'description' => nil,
- 'color' => a_string_matching(/^#\h{6}$/),
- 'text_color' => a_string_matching(/^#\h{6}$/),
+ 'color' => a_valid_color,
+ 'text_color' => a_valid_color,
'priority' => nil,
'subscribed' => false,
'is_project_label' => false)
@@ -277,8 +277,8 @@ RSpec.describe API::Labels do
'open_merge_requests_count' => 1,
'name' => priority_label.name,
'description' => nil,
- 'color' => a_string_matching(/^#\h{6}$/),
- 'text_color' => a_string_matching(/^#\h{6}$/),
+ 'color' => a_valid_color,
+ 'text_color' => a_valid_color,
'priority' => 3,
'subscribed' => false,
'is_project_label' => true)
@@ -336,7 +336,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_label_title_2)
- expect(json_response['color']).to eq('#FFAABB')
+ expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to eq('test')
expect(json_response['priority']).to eq(2)
end
@@ -350,7 +350,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_label_title_2)
- expect(json_response['color']).to eq('#FFAABB')
+ expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to be_nil
expect(json_response['priority']).to be_nil
end
@@ -365,7 +365,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(valid_label_title_2)
- expect(json_response['color']).to eq('#FFAABB')
+ expect(json_response['color']).to be_color('#FFAABB')
expect(json_response['description']).to be_nil
expect(json_response['priority']).to eq(3)
end
@@ -552,7 +552,7 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(label1.name)
- expect(json_response['color']).to eq(label1.color)
+ expect(json_response['color']).to be_color(label1.color.to_s)
end
context 'if group label already exists' do
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 6186a43f992..561d81f9860 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -675,13 +675,13 @@ RSpec.describe API::Members do
end
context 'adding owner to project' do
- it 'returns 403' do
+ it 'returns created status' do
expect do
post api("/projects/#{project.id}/members", maintainer),
params: { user_id: stranger.id, access_level: Member::OWNER }
- expect(response).to have_gitlab_http_status(:bad_request)
- end.not_to change { project.members.count }
+ expect(response).to have_gitlab_http_status(:created)
+ end.to change { project.members.count }.by(1)
end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 3c28aed6cac..455400072bf 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -228,44 +228,83 @@ RSpec.describe API::Notes do
end
let(:request_body) { 'Hi!' }
+ let(:params) { { body: request_body } }
let(:request_path) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes" }
- subject { post api(request_path, user), params: { body: request_body } }
+ subject { post api(request_path, user), params: params }
context 'a command only note' do
- let(:request_body) { "/spend 1h" }
+ context '/spend' do
+ let(:request_body) { "/spend 1h" }
- before do
- project.add_developer(user)
- end
+ before do
+ project.add_developer(user)
+ end
- it 'returns 202 Accepted status' do
- subject
+ it 'returns 202 Accepted status' do
+ subject
- expect(response).to have_gitlab_http_status(:accepted)
- end
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
- it 'does not actually create a new note' do
- expect { subject }.not_to change { Note.where(system: false).count }
- end
+ it 'does not actually create a new note' do
+ expect { subject }.not_to change { Note.where(system: false).count }
+ end
- it 'does however create a system note about the change', :sidekiq_inline do
- expect { subject }.to change { Note.system.count }.by(1)
- end
+ it 'does however create a system note about the change', :sidekiq_inline do
+ expect { subject }.to change { Note.system.count }.by(1)
+ end
+
+ it 'applies the commands' do
+ expect { subject }.to change { merge_request.reset.total_time_spent }
+ end
+
+ it 'reports the changes' do
+ subject
- it 'applies the commands' do
- expect { subject }.to change { merge_request.reset.total_time_spent }
+ expect(json_response).to include(
+ 'commands_changes' => include(
+ 'spend_time' => include('duration' => 3600)
+ ),
+ 'summary' => include('Added 1h spent time.')
+ )
+ end
end
- it 'reports the changes' do
- subject
+ context '/merge' do
+ let(:request_body) { "/merge" }
+ let(:project) { create(:project, :public, :repository) }
+ let(:merge_request) { create(:merge_request_with_multiple_diffs, source_project: project, target_project: project, author: user) }
+ let(:params) { { body: request_body, merge_request_diff_head_sha: merge_request.diff_head_sha } }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns 202 Accepted status' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+
+ it 'does not actually create a new note' do
+ expect { subject }.not_to change { Note.where(system: false).count }
+ end
+
+ it 'applies the commands' do
+ expect { subject }.to change { merge_request.reload.merge_jid.present? }.from(false).to(true)
+ end
- expect(json_response).to include(
- 'commands_changes' => include(
- 'spend_time' => include('duration' => 3600)
- ),
- 'summary' => include('Added 1h spent time.')
- )
+ it 'reports the changes' do
+ subject
+
+ expect(json_response).to include(
+ 'commands_changes' => include(
+ 'merge' => merge_request.diff_head_sha
+ ),
+ 'summary' => ['Merged this merge request.']
+ )
+ end
end
end
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index 8a6e87944ec..02d377efd95 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -9,6 +9,7 @@ itself: # project
- external_webhook_token
- has_external_issue_tracker
- has_external_wiki
+ - hidden
- import_source
- import_type
- import_url
@@ -121,7 +122,6 @@ project_feature:
- created_at
- metrics_dashboard_access_level
- project_id
- - security_and_compliance_access_level
- updated_at
computed_attributes:
- issues_enabled
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index b83b41a881a..4c7da78f0d4 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -24,6 +24,10 @@ RSpec.describe API::ProjectClusters do
projects: [project])
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { get api("/projects/#{project.id}/clusters", developer_user) }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
get api("/projects/#{project.id}/clusters", reporter_user)
@@ -67,6 +71,10 @@ RSpec.describe API::ProjectClusters do
projects: [project])
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { get api("/projects/#{project.id}/clusters/#{cluster_id}", developer_user) }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
get api("/projects/#{project.id}/clusters/#{cluster_id}", reporter_user)
@@ -182,6 +190,10 @@ RSpec.describe API::ProjectClusters do
}
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { post api("/projects/#{project.id}/clusters/user", maintainer_user), params: cluster_params }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
@@ -361,6 +373,10 @@ RSpec.describe API::ProjectClusters do
projects: [project])
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { put api("/projects/#{project.id}/clusters/#{cluster.id}", maintainer_user), params: update_params }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
put api("/projects/#{project.id}/clusters/#{cluster.id}", developer_user), params: update_params
@@ -493,6 +509,10 @@ RSpec.describe API::ProjectClusters do
projects: [project])
end
+ include_examples ':certificate_based_clusters feature flag API responses' do
+ let(:subject) { delete api("/projects/#{project.id}/clusters/#{cluster.id}", maintainer_user), params: cluster_params }
+ end
+
context 'non-authorized user' do
it 'responds with 403' do
delete api("/projects/#{project.id}/clusters/#{cluster.id}", developer_user), params: cluster_params
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 3ed08afd57d..a0f6d3d0081 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe API::ProjectImport do
+RSpec.describe API::ProjectImport, :aggregate_failures do
include WorkhorseHelpers
include AfterNextHelpers
@@ -47,7 +47,7 @@ RSpec.describe API::ProjectImport do
it 'executes a limited number of queries' do
control_count = ActiveRecord::QueryRecorder.new { subject }.count
- expect(control_count).to be <= 104
+ expect(control_count).to be <= 105
end
it 'schedules an import using a namespace' do
@@ -329,7 +329,7 @@ RSpec.describe API::ProjectImport do
)
service_response = ServiceResponse.success(payload: project)
- expect_next(::Import::GitlabProjects::CreateProjectFromRemoteFileService)
+ expect_next(::Import::GitlabProjects::CreateProjectService)
.to receive(:execute)
.and_return(service_response)
@@ -352,7 +352,86 @@ RSpec.describe API::ProjectImport do
message: 'Failed to import',
http_status: :bad_request
)
- expect_next(::Import::GitlabProjects::CreateProjectFromRemoteFileService)
+ expect_next(::Import::GitlabProjects::CreateProjectService)
+ .to receive(:execute)
+ .and_return(service_response)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response).to eq({
+ 'message' => 'Failed to import'
+ })
+ end
+ end
+ end
+ end
+
+ describe 'POST /projects/remote-import-s3' do
+ subject do
+ post api('/projects/remote-import-s3', user), params: params
+ end
+
+ let(:params) do
+ {
+ path: 'test-import',
+ region: 'region_name',
+ bucket_name: 'bucket_name',
+ file_key: 'file_key',
+ access_key_id: 'access_key_id',
+ secret_access_key: 'secret_access_key'
+ }
+ end
+
+ it_behaves_like 'requires authentication'
+
+ it 'returns NOT FOUND when the feature is disabled' do
+ stub_feature_flags(import_project_from_remote_file_s3: false)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'when the feature flag is enabled' do
+ before do
+ stub_feature_flags(import_project_from_remote_file_s3: true)
+ end
+
+ context 'when the response is successful' do
+ it 'schedules the import successfully' do
+ project = create(
+ :project,
+ namespace: user.namespace,
+ name: 'test-import',
+ path: 'test-import'
+ )
+
+ service_response = ServiceResponse.success(payload: project)
+ expect_next(::Import::GitlabProjects::CreateProjectService)
+ .to receive(:execute)
+ .and_return(service_response)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to include({
+ 'id' => project.id,
+ 'name' => 'test-import',
+ 'name_with_namespace' => "#{user.namespace.name} / test-import",
+ 'path' => 'test-import',
+ 'path_with_namespace' => "#{user.namespace.path}/test-import"
+ })
+ end
+ end
+
+ context 'when the service returns an error' do
+ it 'fails to schedule the import' do
+ service_response = ServiceResponse.error(
+ message: 'Failed to import',
+ http_status: :bad_request
+ )
+ expect_next(::Import::GitlabProjects::CreateProjectService)
.to receive(:execute)
.and_return(service_response)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 512cbf7c321..72519ed1683 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -276,7 +276,7 @@ RSpec.describe API::ProjectSnippets do
it 'rejects the snippet' do
expect { subject }.not_to change { Snippet.count }
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to eq({ "error" => "Spam detected" })
+ expect(json_response['message']['error']).to match(/snippet has been recognized as spam/)
end
it 'creates a spam log' do
@@ -344,7 +344,7 @@ RSpec.describe API::ProjectSnippets do
.not_to change { snippet.reload.title }
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to eq({ "error" => "Spam detected" })
+ expect(json_response['message']['error']).to match(/snippet has been recognized as spam/)
end
it 'creates a spam log' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 02df82d14a8..fc1d815a64e 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1077,6 +1077,7 @@ RSpec.describe API::Projects do
attrs[:operations_access_level] = 'disabled'
attrs[:analytics_access_level] = 'disabled'
attrs[:container_registry_access_level] = 'private'
+ attrs[:security_and_compliance_access_level] = 'private'
end
post api('/projects', user), params: project
@@ -1100,6 +1101,7 @@ RSpec.describe API::Projects do
expect(project.operations_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.analytics_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.container_registry_access_level).to eq(ProjectFeature::PRIVATE)
+ expect(project.project_feature.security_and_compliance_access_level).to eq(ProjectFeature::PRIVATE)
end
it 'assigns container_registry_enabled to project', :aggregate_failures do
@@ -2227,6 +2229,7 @@ RSpec.describe API::Projects do
expect(json_response['restrict_user_defined_variables']).to eq(project.restrict_user_defined_variables?)
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
expect(json_response['operations_access_level']).to be_present
+ expect(json_response['security_and_compliance_access_level']).to be_present
end
it 'exposes all necessary attributes' do
@@ -2295,6 +2298,7 @@ RSpec.describe API::Projects do
expect(json_response['wiki_access_level']).to be_present
expect(json_response['builds_access_level']).to be_present
expect(json_response['operations_access_level']).to be_present
+ expect(json_response['security_and_compliance_access_level']).to be_present
expect(json_response).to have_key('emails_disabled')
expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['remove_source_branch_after_merge']).to be_truthy
@@ -2542,9 +2546,11 @@ RSpec.describe API::Projects do
get api("/projects", user)
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.first['permissions']['project_access']['access_level'])
+ detail_of_project = json_response.find { |detail| detail['id'] == project.id }
+
+ expect(detail_of_project.dig('permissions', 'project_access', 'access_level'))
.to eq(Gitlab::Access::MAINTAINER)
- expect(json_response.first['permissions']['group_access']).to be_nil
+ expect(detail_of_project.dig('permissions', 'group_access')).to be_nil
end
end
@@ -3220,6 +3226,30 @@ RSpec.describe API::Projects do
expect(project.reload.container_registry_access_level).to eq(ProjectFeature::ENABLED)
end
+ it 'sets security_and_compliance_access_level', :aggregate_failures do
+ put api("/projects/#{project.id}", user), params: { security_and_compliance_access_level: 'private' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['security_and_compliance_access_level']).to eq('private')
+ expect(Project.find_by(path: project[:path]).security_and_compliance_access_level).to eq(ProjectFeature::PRIVATE)
+ end
+
+ it 'sets operations_access_level', :aggregate_failures do
+ put api("/projects/#{project.id}", user), params: { operations_access_level: 'private' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['operations_access_level']).to eq('private')
+ expect(Project.find_by(path: project[:path]).operations_access_level).to eq(ProjectFeature::PRIVATE)
+ end
+
+ it 'sets analytics_access_level', :aggregate_failures do
+ put api("/projects/#{project.id}", user), params: { analytics_access_level: 'private' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['analytics_access_level']).to eq('private')
+ expect(Project.find_by(path: project[:path]).analytics_access_level).to eq(ProjectFeature::PRIVATE)
+ end
+
it 'returns 400 when nothing sent' do
project_param = {}
diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb
index fcd2d56e655..078db4f1509 100644
--- a/spec/requests/api/pypi_packages_spec.rb
+++ b/spec/requests/api/pypi_packages_spec.rb
@@ -185,6 +185,14 @@ RSpec.describe API::PypiPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
+
+ context 'without requires_python' do
+ let(:token) { personal_access_token.token }
+ let(:user_headers) { basic_auth_header(user.username, token) }
+ let(:headers) { user_headers.merge(workhorse_headers) }
+
+ it_behaves_like 'PyPI package creation', :developer, :created, true
+ end
end
context 'with required_python too big' do
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index cb9b6a072b1..6038682de1e 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -160,7 +160,7 @@ RSpec.describe API::Releases do
get api("/projects/#{project.id}/releases", maintainer)
end.count
- create_list(:release, 2, :with_evidence, project: project, tag: 'v0.1', author: maintainer)
+ create_list(:release, 2, :with_evidence, project: project, author: maintainer)
create_list(:release, 2, project: project)
create_list(:release_link, 2, release: project.releases.first)
create_list(:release_link, 2, release: project.releases.last)
@@ -467,10 +467,10 @@ RSpec.describe API::Releases do
it "exposes tag and commit" do
create(:release,
project: project,
- tag: 'v0.1',
+ tag: 'v0.0.1',
author: maintainer,
created_at: 2.days.ago)
- get api("/projects/#{project.id}/releases/v0.1", guest)
+ get api("/projects/#{project.id}/releases/v0.0.1", guest)
expect(response).to match_response_schema('public_api/v4/release')
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index f42fc7aabc2..1d199a72d1d 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -783,6 +783,13 @@ RSpec.describe API::Repositories do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['notes']).to be_present
end
+
+ context 'when previous tag version does not exist' do
+ it_behaves_like '422 response' do
+ let(:request) { get api("/projects/#{project.id}/repository/changelog", user), params: { version: 'v0.0.0' } }
+ let(:message) { 'Failed to generate the changelog: The commit start range is unspecified, and no previous tag could be found to use instead' }
+ end
+ end
end
describe 'POST /projects/:id/repository/changelog' do
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 24cd95781c3..4d2a69cd85b 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -8,6 +8,11 @@ RSpec.describe API::Search do
let_it_be(:project, reload: true) { create(:project, :wiki_repo, :public, name: 'awesome project', group: group) }
let_it_be(:repo_project) { create(:project, :public, :repository, group: group) }
+ before do
+ allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).with(:search_rate_limit).and_return(1000)
+ allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).with(:search_rate_limit_unauthenticated).and_return(1000)
+ end
+
shared_examples 'response is correct' do |schema:, size: 1|
it { expect(response).to have_gitlab_http_status(:ok) }
it { expect(response).to match_response_schema(schema) }
@@ -347,7 +352,7 @@ RSpec.describe API::Search do
end
end
- it_behaves_like 'rate limited endpoint', rate_limit_key: :user_email_lookup do
+ it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do
let(:current_user) { user }
def request
@@ -522,7 +527,7 @@ RSpec.describe API::Search do
it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
end
- it_behaves_like 'rate limited endpoint', rate_limit_key: :user_email_lookup do
+ it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do
let(:current_user) { user }
def request
@@ -803,7 +808,7 @@ RSpec.describe API::Search do
end
end
- it_behaves_like 'rate limited endpoint', rate_limit_key: :user_email_lookup do
+ it_behaves_like 'rate limited endpoint', rate_limit_key: :search_rate_limit do
let(:current_user) { user }
def request
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index dd5e6ac8a5e..13160519996 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -325,7 +325,7 @@ RSpec.describe API::Snippets, factory_default: :keep do
expect { subject }.not_to change { Snippet.count }
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to eq({ "error" => "Spam detected" })
+ expect(json_response['message']['error']).to match(/snippet has been recognized as spam/)
end
it 'creates a spam log' do
@@ -392,7 +392,7 @@ RSpec.describe API::Snippets, factory_default: :keep do
.not_to change { snippet.reload.title }
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to eq({ "error" => "Spam detected" })
+ expect(json_response['message']['error']).to match(/snippet has been recognized as spam/)
end
it 'creates a spam log' do
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 1511872d183..d94b70ec0f9 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -36,12 +36,57 @@ RSpec.describe API::SystemHooks do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect(response).to match_response_schema('public_api/v4/system_hooks')
+ expect(json_response.first).not_to have_key("token")
expect(json_response.first['url']).to eq(hook.url)
expect(json_response.first['push_events']).to be false
expect(json_response.first['tag_push_events']).to be false
expect(json_response.first['merge_requests_events']).to be false
expect(json_response.first['repository_update_events']).to be true
+ expect(json_response.first['enable_ssl_verification']).to be true
+ end
+ end
+ end
+
+ describe "GET /hooks/:id" do
+ context "when no user" do
+ it "returns authentication error" do
+ get api("/hooks/#{hook.id}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context "when not an admin" do
+ it "returns forbidden error" do
+ get api("/hooks/#{hook.id}", user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context "when authenticated as admin" do
+ it "gets a hook", :aggregate_failures do
+ get api("/hooks/#{hook.id}", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/system_hook')
+ expect(json_response).to match(
+ 'id' => be(hook.id),
+ 'url' => eq(hook.url),
+ 'created_at' => eq(hook.created_at.iso8601(3)),
+ 'push_events' => be(hook.push_events),
+ 'tag_push_events' => be(hook.tag_push_events),
+ 'merge_requests_events' => be(hook.merge_requests_events),
+ 'repository_update_events' => be(hook.repository_update_events),
+ 'enable_ssl_verification' => be(hook.enable_ssl_verification)
+ )
+ end
+
+ it 'returns 404 if the system hook does not exist' do
+ get api("/hooks/#{non_existing_record_id}", admin)
+
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
end
@@ -77,6 +122,7 @@ RSpec.describe API::SystemHooks do
post api('/hooks', admin), params: { url: 'http://mep.mep' }
expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('public_api/v4/system_hook')
expect(json_response['enable_ssl_verification']).to be true
expect(json_response['push_events']).to be false
expect(json_response['tag_push_events']).to be false
@@ -98,6 +144,7 @@ RSpec.describe API::SystemHooks do
}
expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('public_api/v4/system_hook')
expect(json_response['enable_ssl_verification']).to be false
expect(json_response['push_events']).to be true
expect(json_response['tag_push_events']).to be true
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index 24f38b04348..ae1e461d433 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -36,8 +36,8 @@ RSpec.describe API::Terraform::State do
let(:current_user) { maintainer }
it_behaves_like 'tracking unique hll events' do
- let(:target_id) { 'p_terraform_state_api_unique_users' }
- let(:expected_type) { instance_of(Integer) }
+ let(:target_event) { 'p_terraform_state_api_unique_users' }
+ let(:expected_value) { instance_of(Integer) }
end
end
end
diff --git a/spec/requests/api/topics_spec.rb b/spec/requests/api/topics_spec.rb
index 70eee8a1af9..5c17ca9581e 100644
--- a/spec/requests/api/topics_spec.rb
+++ b/spec/requests/api/topics_spec.rb
@@ -7,9 +7,9 @@ RSpec.describe API::Topics do
let_it_be(:file) { fixture_file_upload('spec/fixtures/dk.png') }
- let_it_be(:topic_1) { create(:topic, name: 'Git', total_projects_count: 1, avatar: file) }
- let_it_be(:topic_2) { create(:topic, name: 'GitLab', total_projects_count: 2) }
- let_it_be(:topic_3) { create(:topic, name: 'other-topic', total_projects_count: 3) }
+ let_it_be(:topic_1) { create(:topic, name: 'Git', total_projects_count: 1, non_private_projects_count: 1, avatar: file) }
+ let_it_be(:topic_2) { create(:topic, name: 'GitLab', total_projects_count: 2, non_private_projects_count: 2) }
+ let_it_be(:topic_3) { create(:topic, name: 'other-topic', total_projects_count: 3, non_private_projects_count: 3) }
let_it_be(:admin) { create(:user, :admin) }
let_it_be(:user) { create(:user) }
@@ -142,6 +142,13 @@ RSpec.describe API::Topics do
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eql('name is missing')
end
+
+ it 'returns 400 if name is not unique (case insensitive)' do
+ post api('/topics/', admin), params: { name: topic_1.name.downcase }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']['name']).to eq(['has already been taken'])
+ end
end
context 'as normal user' do
@@ -248,4 +255,43 @@ RSpec.describe API::Topics do
end
end
end
+
+ describe 'DELETE /topics', :aggregate_failures do
+ context 'as administrator' do
+ it 'deletes a topic' do
+ delete api("/topics/#{topic_3.id}", admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'returns 404 for non existing id' do
+ delete api("/topics/#{non_existing_record_id}", admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 400 for invalid `id` parameter' do
+ delete api('/topics/invalid', admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eql('id is invalid')
+ end
+ end
+
+ context 'as normal user' do
+ it 'returns 403 Forbidden' do
+ delete api("/topics/#{topic_3.id}", user), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'as anonymous' do
+ it 'returns 401 Unauthorized' do
+ delete api("/topics/#{topic_3.id}"), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb
index ab2aa87d1b7..27ebf02dd81 100644
--- a/spec/requests/api/user_counts_spec.rb
+++ b/spec/requests/api/user_counts_spec.rb
@@ -43,6 +43,21 @@ RSpec.describe API::UserCounts do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_a Hash
expect(json_response['merge_requests']).to eq(2)
+ expect(json_response['attention_requests']).to eq(2)
+ end
+
+ describe 'mr_attention_requests is disabled' do
+ before do
+ stub_feature_flags(mr_attention_requests: false)
+ end
+
+ it 'does not include attention_requests count' do
+ create(:merge_request, source_project: project, author: user, assignees: [user])
+
+ get api('/user_counts', user)
+
+ expect(json_response.key?('attention_requests')).to be(false)
+ end
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 985e07bf174..2d71674273b 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -11,6 +11,7 @@ RSpec.describe API::Users do
let(:blocked_user) { create(:user, :blocked) }
let(:omniauth_user) { create(:omniauth_user) }
+ let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') }
let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
let(:private_user) { create(:user, private_profile: true) }
let(:deactivated_user) { create(:user, state: 'deactivated') }
@@ -649,20 +650,6 @@ RSpec.describe API::Users do
expect(response).to have_gitlab_http_status(:ok)
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(rate_limit_user_by_id_endpoint: false)
- end
-
- it 'does not throttle the request' do
- expect(Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
-
- get api("/users/#{user.id}", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
end
context 'when job title is present' do
@@ -1307,10 +1294,10 @@ RSpec.describe API::Users do
end
it "updates user's existing identity" do
- put api("/users/#{omniauth_user.id}", admin), params: { provider: 'ldapmain', extern_uid: '654321' }
+ put api("/users/#{ldap_user.id}", admin), params: { provider: 'ldapmain', extern_uid: '654321' }
expect(response).to have_gitlab_http_status(:ok)
- expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321')
+ expect(ldap_user.reload.identities.first.extern_uid).to eq('654321')
end
it 'updates user with new identity' do
@@ -1735,6 +1722,33 @@ RSpec.describe API::Users do
end
end
+ describe 'GET /user/:id/keys/:key_id' do
+ it 'gets existing key', :aggregate_failures do
+ user.keys << key
+
+ get api("/users/#{user.id}/keys/#{key.id}")
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['title']).to eq(key.title)
+ end
+
+ it 'returns 404 error if user not found', :aggregate_failures do
+ user.keys << key
+
+ get api("/users/0/keys/#{key.id}")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns 404 error if key not found', :aggregate_failures do
+ get api("/users/#{user.id}/keys/#{non_existing_record_id}")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Key Not Found')
+ end
+ end
+
describe 'DELETE /user/:id/keys/:key_id' do
context 'when unauthenticated' do
it 'returns authentication error' do
@@ -3103,6 +3117,18 @@ RSpec.describe API::Users do
expect(response.body).to eq('null')
end
end
+
+ context 'with the API initiating user' do
+ let(:user_id) { admin.id }
+
+ it 'does not block the API initiating user, returns 403' do
+ block_user
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq('403 Forbidden - The API initiating user cannot be blocked by the API')
+ expect(admin.reload.state).to eq('active')
+ end
+ end
end
it 'is not available for non admin users' do
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index ec34dc7e7a1..06ae61ca5eb 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe API::Wikis do
let(:project_wiki) { create(:project_wiki, project: project, user: user) }
let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
- let(:expected_keys_with_content) { %w(content format slug title) }
+ let(:expected_keys_with_content) { %w(content format slug title encoding) }
let(:expected_keys_without_content) { %w(format slug title) }
let(:wiki) { project_wiki }
@@ -130,41 +130,42 @@ RSpec.describe API::Wikis do
describe 'GET /projects/:id/wikis/:slug' do
let(:page) { create(:wiki_page, wiki: project.wiki) }
let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" }
+ let(:params) { {} }
+
+ subject(:request) { get api(url, user), params: params }
context 'when wiki is disabled' do
let(:project) { project_wiki_disabled }
+ before do
+ request
+ end
+
context 'when user is guest' do
- before do
- get api(url)
- end
+ let(:user) { nil }
include_examples 'wiki API 404 Project Not Found'
end
context 'when user is developer' do
- before do
- get api(url, developer)
- end
+ let(:user) { developer }
include_examples 'wiki API 403 Forbidden'
end
context 'when user is maintainer' do
- before do
- get api(url, maintainer)
- end
+ let(:user) { maintainer }
include_examples 'wiki API 403 Forbidden'
end
end
context 'when wiki is available only for team members' do
- let(:project) { create(:project, :wiki_repo, :wiki_private) }
+ let_it_be_with_reload(:project) { create(:project, :wiki_repo, :wiki_private) }
context 'when user is guest' do
before do
- get api(url)
+ request
end
include_examples 'wiki API 404 Project Not Found'
@@ -173,7 +174,6 @@ RSpec.describe API::Wikis do
context 'when user is developer' do
before do
project.add_developer(user)
- get api(url, user)
end
include_examples 'wikis API returns wiki page'
@@ -181,6 +181,10 @@ RSpec.describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ request
+ end
+
include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -188,8 +192,6 @@ RSpec.describe API::Wikis do
context 'when user is maintainer' do
before do
project.add_maintainer(user)
-
- get api(url, user)
end
include_examples 'wikis API returns wiki page'
@@ -197,17 +199,23 @@ RSpec.describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ request
+ end
+
include_examples 'wiki API 404 Wiki Page Not Found'
end
end
end
context 'when wiki is available for everyone with access' do
- let(:project) { create(:project, :wiki_repo) }
+ let_it_be_with_reload(:project) { create(:project, :wiki_repo) }
context 'when user is guest' do
+ let(:user) { nil }
+
before do
- get api(url)
+ request
end
include_examples 'wiki API 404 Project Not Found'
@@ -216,8 +224,6 @@ RSpec.describe API::Wikis do
context 'when user is developer' do
before do
project.add_developer(user)
-
- get api(url, user)
end
include_examples 'wikis API returns wiki page'
@@ -225,6 +231,10 @@ RSpec.describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ request
+ end
+
include_examples 'wiki API 404 Wiki Page Not Found'
end
end
@@ -232,8 +242,6 @@ RSpec.describe API::Wikis do
context 'when user is maintainer' do
before do
project.add_maintainer(user)
-
- get api(url, user)
end
include_examples 'wikis API returns wiki page'
@@ -241,6 +249,10 @@ RSpec.describe API::Wikis do
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
+ before do
+ request
+ end
+
include_examples 'wiki API 404 Wiki Page Not Found'
end
end
diff --git a/spec/requests/content_security_policy_spec.rb b/spec/requests/content_security_policy_spec.rb
new file mode 100644
index 00000000000..06fc5b0e190
--- /dev/null
+++ b/spec/requests/content_security_policy_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# The AnonymousController doesn't support setting the CSP
+# This is why an arbitrary test request was chosen instead
+# of testing in application_controller_spec.
+RSpec.describe 'Content Security Policy' do
+ let(:snowplow_host) { 'snowplow.example.com' }
+
+ shared_examples 'snowplow is not in the CSP' do
+ it 'does not add the snowplow collector hostname to the CSP' do
+ get explore_root_url
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['Content-Security-Policy']).not_to include(snowplow_host)
+ end
+ end
+
+ describe 'GET #explore' do
+ context 'snowplow is enabled' do
+ before do
+ stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: snowplow_host)
+ end
+
+ it 'adds the snowplow collector hostname to the CSP' do
+ get explore_root_url
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['Content-Security-Policy']).to include(snowplow_host)
+ end
+ end
+
+ context 'snowplow is enabled but host is not configured' do
+ before do
+ stub_application_setting(snowplow_enabled: true)
+ end
+
+ it_behaves_like 'snowplow is not in the CSP'
+ end
+
+ context 'snowplow is disabled' do
+ before do
+ stub_application_setting(snowplow_enabled: false, snowplow_collector_hostname: snowplow_host)
+ end
+
+ it_behaves_like 'snowplow is not in the CSP'
+ end
+ end
+end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 340ed7bde53..9f9e1cfd90e 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -1019,7 +1019,11 @@ RSpec.describe 'Git HTTP requests' do
let(:path) { "#{project.full_path}.git" }
context "when the project is public" do
- let(:project) { create(:project, :repository, :public, path: 'foo.') }
+ let(:project) do
+ project = create(:project, :repository, :public)
+ project.update_attribute(:path, 'foo.')
+ project
+ end
it_behaves_like 'pushes require Basic HTTP Authentication'
@@ -1158,7 +1162,11 @@ RSpec.describe 'Git HTTP requests' do
end
context "when the project is private" do
- let(:project) { create(:project, :repository, :private, path: 'foo.') }
+ let(:project) do
+ project = create(:project, :repository, :private)
+ project.update_attribute(:path, 'foo.')
+ project
+ end
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
@@ -1586,11 +1594,19 @@ RSpec.describe 'Git HTTP requests' do
end
it_behaves_like 'project path without .git suffix' do
- let(:repository_path) { create(:project, :repository, :public, path: 'project.').full_path }
+ let(:repository_path) do
+ project = create(:project, :repository, :public)
+ project.update_attribute(:path, 'project.')
+ project.full_path
+ end
end
context "retrieving an info/refs file" do
- let(:project) { create(:project, :repository, :public, path: 'project.') }
+ let(:project) do
+ project = create(:project, :repository, :public)
+ project.update_attribute(:path, 'project.')
+ project
+ end
context "when the file exists" do
before do
@@ -1625,7 +1641,11 @@ RSpec.describe 'Git HTTP requests' do
let(:path) { "/#{wiki.repository.full_path}.git" }
context "when the project is public" do
- let(:project) { create(:project, :wiki_repo, :public, :wiki_enabled, path: 'foo.') }
+ let(:project) do
+ project = create(:project, :wiki_repo, :public, :wiki_enabled)
+ project.update_attribute(:path, 'foo.')
+ project
+ end
it_behaves_like 'pushes require Basic HTTP Authentication'
@@ -1652,7 +1672,11 @@ RSpec.describe 'Git HTTP requests' do
end
context 'but the repo is disabled' do
- let(:project) { create(:project, :wiki_repo, :public, :repository_disabled, :wiki_enabled, path: 'foo.') }
+ let(:project) do
+ project = create(:project, :wiki_repo, :public, :repository_disabled, :wiki_enabled)
+ project.update_attribute(:path, 'foo.')
+ project
+ end
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
@@ -1673,7 +1697,11 @@ RSpec.describe 'Git HTTP requests' do
end
context "when the project is private" do
- let(:project) { create(:project, :wiki_repo, :private, :wiki_enabled, path: 'foo.') }
+ let(:project) do
+ project = create(:project, :wiki_repo, :private, :wiki_enabled)
+ project.update_attribute(:path, 'foo.')
+ project
+ end
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
@@ -1700,7 +1728,11 @@ RSpec.describe 'Git HTTP requests' do
end
context 'but the repo is disabled' do
- let(:project) { create(:project, :wiki_repo, :private, :repository_disabled, :wiki_enabled, path: 'foo.') }
+ let(:project) do
+ project = create(:project, :wiki_repo, :private, :repository_disabled, :wiki_enabled)
+ project.update_attribute(:path, 'foo.')
+ project
+ end
it 'allows clones' do
download(path, user: user.username, password: user.password) do |response|
diff --git a/spec/requests/groups/crm/contacts_controller_spec.rb b/spec/requests/groups/crm/contacts_controller_spec.rb
index 5d126c6ead5..4d8ca0fcd60 100644
--- a/spec/requests/groups/crm/contacts_controller_spec.rb
+++ b/spec/requests/groups/crm/contacts_controller_spec.rb
@@ -49,6 +49,12 @@ RSpec.describe Groups::Crm::ContactsController do
it_behaves_like 'response with 404 status'
end
+
+ context 'when subgroup' do
+ let(:group) { create(:group, :private, :crm_enabled, parent: create(:group)) }
+
+ it_behaves_like 'response with 404 status'
+ end
end
context 'with unauthorized user' do
diff --git a/spec/requests/groups/crm/organizations_controller_spec.rb b/spec/requests/groups/crm/organizations_controller_spec.rb
index f38300c3c5b..37ffac71772 100644
--- a/spec/requests/groups/crm/organizations_controller_spec.rb
+++ b/spec/requests/groups/crm/organizations_controller_spec.rb
@@ -49,6 +49,12 @@ RSpec.describe Groups::Crm::OrganizationsController do
it_behaves_like 'response with 404 status'
end
+
+ context 'when subgroup' do
+ let(:group) { create(:group, :private, :crm_enabled, parent: create(:group)) }
+
+ it_behaves_like 'response with 404 status'
+ end
end
context 'with unauthorized user' do
diff --git a/spec/requests/groups/deploy_tokens_controller_spec.rb b/spec/requests/groups/deploy_tokens_controller_spec.rb
new file mode 100644
index 00000000000..b3dce9b9cf1
--- /dev/null
+++ b/spec/requests/groups/deploy_tokens_controller_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::DeployTokensController do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:deploy_token) { create(:deploy_token, :group, groups: [group]) }
+ let_it_be(:params) do
+ { id: deploy_token.id, group_id: group }
+ end
+
+ before do
+ group.add_owner(user)
+
+ sign_in(user)
+ end
+
+ describe 'PUT /groups/:group_path_with_namespace/-/deploy_tokens/:id/revoke' do
+ subject(:put_revoke) do
+ put "/groups/#{group.full_path}/-/deploy_tokens/#{deploy_token.id}/revoke", params: params
+ end
+
+ it 'invokes the Groups::DeployTokens::RevokeService' do
+ expect(deploy_token.revoked).to eq(false)
+ expect(Groups::DeployTokens::RevokeService).to receive(:new).and_call_original
+
+ put_revoke
+
+ expect(deploy_token.reload.revoked).to eq(true)
+ end
+
+ it 'redirects to group repository settings with correct anchor' do
+ put_revoke
+
+ expect(response).to have_gitlab_http_status(:redirect)
+ expect(response).to redirect_to(group_settings_repository_path(group, anchor: 'js-deploy-tokens'))
+ end
+ end
+end
diff --git a/spec/requests/groups/harbor/repositories_controller_spec.rb b/spec/requests/groups/harbor/repositories_controller_spec.rb
new file mode 100644
index 00000000000..3e475dc410e
--- /dev/null
+++ b/spec/requests/groups/harbor/repositories_controller_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::Harbor::RepositoriesController do
+ let_it_be(:group, reload: true) { create(:group) }
+ let_it_be(:user) { create(:user) }
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ end
+ end
+
+ before do
+ stub_feature_flags(harbor_registry_integration: true)
+ group.add_reporter(user)
+ login_as(user)
+ end
+
+ describe 'GET #index' do
+ subject do
+ get group_harbor_registries_path(group)
+ response
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+
+ describe 'GET #show' do
+ subject do
+ get group_harbor_registry_path(group, 1)
+ response
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+end
diff --git a/spec/requests/jira_connect/oauth_callbacks_controller_spec.rb b/spec/requests/jira_connect/oauth_callbacks_controller_spec.rb
new file mode 100644
index 00000000000..1e4628e5d59
--- /dev/null
+++ b/spec/requests/jira_connect/oauth_callbacks_controller_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe JiraConnect::OauthCallbacksController do
+ describe 'GET /-/jira_connect/oauth_callbacks' do
+ context 'when logged in' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'renders a page prompting the user to close the window' do
+ get '/-/jira_connect/oauth_callbacks'
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to include('You can close this window.')
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects/google_cloud/deployments_controller_spec.rb b/spec/requests/projects/google_cloud/deployments_controller_spec.rb
index fd356bc61c7..7bd9609a7dc 100644
--- a/spec/requests/projects/google_cloud/deployments_controller_spec.rb
+++ b/spec/requests/projects/google_cloud/deployments_controller_spec.rb
@@ -22,13 +22,21 @@ RSpec.describe Projects::GoogleCloud::DeploymentsController do
project.add_maintainer(user_maintainer)
end
- describe "Routes must be restricted behind Google OAuth2" do
+ describe "Routes must be restricted behind Google OAuth2", :snowplow do
context 'when a public request is made' do
it 'returns not found on GET request' do
urls_list.each do |url|
get url
expect(response).to have_gitlab_http_status(:not_found)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: nil
+ )
end
end
end
@@ -40,6 +48,14 @@ RSpec.describe Projects::GoogleCloud::DeploymentsController do
get url
expect(response).to have_gitlab_http_status(:not_found)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: nil
+ )
end
end
end
@@ -60,7 +76,7 @@ RSpec.describe Projects::GoogleCloud::DeploymentsController do
end
end
- describe 'Authorized GET project/-/google_cloud/deployments/cloud_run' do
+ describe 'Authorized GET project/-/google_cloud/deployments/cloud_run', :snowplow do
let_it_be(:url) { "#{project_google_cloud_deployments_cloud_run_path(project)}" }
before do
@@ -72,25 +88,39 @@ RSpec.describe Projects::GoogleCloud::DeploymentsController do
end
it 'redirects to google_cloud home on enable service error' do
- # since GPC_PROJECT_ID is not set, enable cloud run service should return an error
-
get url
expect(response).to redirect_to(project_google_cloud_index_path(project))
+ # since GPC_PROJECT_ID is not set, enable cloud run service should return an error
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'deployments#cloud_run',
+ label: 'enable_cloud_run_error',
+ extra: { message: 'No GCP projects found. Configure a service account or GCP_PROJECT_ID ci variable.',
+ status: :error },
+ project: project,
+ user: user_maintainer
+ )
end
- it 'tracks error and redirects to gcp_error' do
- mock_google_error = Google::Apis::ClientError.new('some_error')
+ it 'redirects to gcp_error' do
+ mock_gcp_error = Google::Apis::ClientError.new('some_error')
allow_next_instance_of(GoogleCloud::EnableCloudRunService) do |service|
- allow(service).to receive(:execute).and_raise(mock_google_error)
+ allow(service).to receive(:execute).and_raise(mock_gcp_error)
end
- expect(Gitlab::ErrorTracking).to receive(:track_exception).with(mock_google_error, { project_id: project.id })
-
get url
expect(response).to render_template(:gcp_error)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'deployments#cloud_run',
+ label: 'gcp_error',
+ extra: mock_gcp_error,
+ project: project,
+ user: user_maintainer
+ )
end
context 'GCP_PROJECT_IDs are defined' do
@@ -106,6 +136,14 @@ RSpec.describe Projects::GoogleCloud::DeploymentsController do
get url
expect(response).to redirect_to(project_google_cloud_index_path(project))
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'deployments#cloud_run',
+ label: 'generate_pipeline_error',
+ extra: { status: :error },
+ project: project,
+ user: user_maintainer
+ )
end
it 'redirects to create merge request form' do
@@ -121,11 +159,24 @@ RSpec.describe Projects::GoogleCloud::DeploymentsController do
expect(response).to have_gitlab_http_status(:found)
expect(response.location).to include(project_new_merge_request_path(project))
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'deployments#cloud_run',
+ label: 'cloud_run_success',
+ extra: { "title": "Enable deployments to Cloud Run",
+ "description": "This merge request includes a Cloud Run deployment job in the pipeline definition (.gitlab-ci.yml).\n\nThe `deploy-to-cloud-run` job:\n* Requires the following environment variables\n * `GCP_PROJECT_ID`\n * `GCP_SERVICE_ACCOUNT_KEY`\n* Job definition can be found at: https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/library\n\nThis pipeline definition has been committed to the branch ``.\nYou may modify the pipeline definition further or accept the changes as-is if suitable.\n",
+ "source_project_id": project.id,
+ "target_project_id": project.id,
+ "source_branch": nil,
+ "target_branch": project.default_branch },
+ project: project,
+ user: user_maintainer
+ )
end
end
end
- describe 'Authorized GET project/-/google_cloud/deployments/cloud_storage' do
+ describe 'Authorized GET project/-/google_cloud/deployments/cloud_storage', :snowplow do
let_it_be(:url) { "#{project_google_cloud_deployments_cloud_storage_path(project)}" }
before do
diff --git a/spec/requests/projects/google_cloud/gcp_regions_controller_spec.rb b/spec/requests/projects/google_cloud/gcp_regions_controller_spec.rb
new file mode 100644
index 00000000000..56474b6520d
--- /dev/null
+++ b/spec/requests/projects/google_cloud/gcp_regions_controller_spec.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::GoogleCloud::GcpRegionsController do
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:repository) { project.repository }
+
+ let(:user_guest) { create(:user) }
+ let(:user_maintainer) { create(:user) }
+
+ RSpec.shared_examples "should track not_found event" do
+ it "tracks event" do
+ is_expected.to be(404)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: nil
+ )
+ end
+ end
+
+ RSpec.shared_examples "should track access_denied event" do
+ it "tracks event" do
+ is_expected.to be(404)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: nil
+ )
+ end
+ end
+
+ RSpec.shared_examples "should track feature_flag_disabled event" do |user|
+ it "tracks event" do
+ is_expected.to be(404)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'feature_flag_enabled!',
+ label: 'access_denied',
+ property: 'feature_flag_not_enabled',
+ project: project,
+ user: user_maintainer
+ )
+ end
+ end
+
+ RSpec.shared_examples "should track gcp_error event" do |config|
+ it "tracks event" do
+ is_expected.to be(403)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'google_oauth2_enabled!',
+ label: 'access_denied',
+ extra: { reason: 'google_oauth2_not_configured', config: config },
+ project: project,
+ user: user_maintainer
+ )
+ end
+ end
+
+ RSpec.shared_examples "should be not found" do
+ it 'returns not found' do
+ is_expected.to be(404)
+ end
+ end
+
+ RSpec.shared_examples "should be forbidden" do
+ it 'returns forbidden' do
+ is_expected.to be(403)
+ end
+ end
+
+ RSpec.shared_examples "public request should 404" do
+ it_behaves_like "should be not found"
+ it_behaves_like "should track not_found event"
+ end
+
+ RSpec.shared_examples "unauthorized access should 404" do
+ before do
+ project.add_guest(user_guest)
+ end
+
+ it_behaves_like "should be not found"
+ it_behaves_like "should track access_denied event"
+ end
+
+ describe 'GET #index', :snowplow do
+ subject { get project_google_cloud_gcp_regions_path(project) }
+
+ it_behaves_like "public request should 404"
+ it_behaves_like "unauthorized access should 404"
+
+ context 'when authorized members make requests' do
+ before do
+ project.add_maintainer(user_maintainer)
+ sign_in(user_maintainer)
+ end
+
+ it 'renders gcp_regions' do
+ is_expected.to render_template('projects/google_cloud/gcp_regions/index')
+ end
+
+ context 'but gitlab instance is not configured for google oauth2' do
+ unconfigured_google_oauth2 = Struct.new(:app_id, :app_secret)
+ .new('', '')
+
+ before do
+ allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for)
+ .with('google_oauth2')
+ .and_return(unconfigured_google_oauth2)
+ end
+
+ it_behaves_like "should be forbidden"
+ it_behaves_like "should track gcp_error event", unconfigured_google_oauth2
+ end
+
+ context 'but feature flag is disabled' do
+ before do
+ stub_feature_flags(incubation_5mp_google_cloud: false)
+ end
+
+ it_behaves_like "should be not found"
+ it_behaves_like "should track feature_flag_disabled event"
+ end
+ end
+ end
+
+ describe 'POST #index', :snowplow do
+ subject { post project_google_cloud_gcp_regions_path(project), params: { gcp_region: 'region1', environment: 'env1' } }
+
+ it_behaves_like "public request should 404"
+ it_behaves_like "unauthorized access should 404"
+
+ context 'when authorized members make requests' do
+ before do
+ project.add_maintainer(user_maintainer)
+ sign_in(user_maintainer)
+ end
+
+ it 'redirects to google cloud index' do
+ is_expected.to redirect_to(project_google_cloud_index_path(project))
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects/google_cloud/revoke_oauth_controller_spec.rb b/spec/requests/projects/google_cloud/revoke_oauth_controller_spec.rb
new file mode 100644
index 00000000000..07590d3710e
--- /dev/null
+++ b/spec/requests/projects/google_cloud/revoke_oauth_controller_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::GoogleCloud::RevokeOauthController do
+ include SessionHelpers
+
+ describe 'POST #create', :snowplow, :clean_gitlab_redis_sessions, :aggregate_failures do
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:url) { project_google_cloud_revoke_oauth_index_path(project).to_s }
+
+ let(:user) { project.creator }
+
+ before do
+ sign_in(user)
+
+ stub_session(GoogleApi::CloudPlatform::Client.session_key_for_token => 'token')
+
+ allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
+ allow(client).to receive(:validate_token).and_return(true)
+ end
+ end
+
+ context 'when GCP token is invalid' do
+ before do
+ allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
+ allow(client).to receive(:validate_token).and_return(false)
+ end
+ end
+
+ it 'redirects to Google OAuth2 authorize URL' do
+ sign_in(user)
+
+ post url
+
+ expect(response).to redirect_to(assigns(:authorize_url))
+ end
+ end
+
+ context 'when revocation is successful' do
+ before do
+ stub_request(:post, "https://oauth2.googleapis.com/revoke")
+ .to_return(status: 200, body: "", headers: {})
+ end
+
+ it 'calls revoke endpoint and redirects' do
+ post url
+
+ expect(request.session[GoogleApi::CloudPlatform::Client.session_key_for_token]).to be_nil
+ expect(response).to redirect_to(project_google_cloud_index_path(project))
+ expect(flash[:notice]).to eq('Google OAuth2 token revocation requested')
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'revoke_oauth#create',
+ label: 'create',
+ property: 'success',
+ project: project,
+ user: user
+ )
+ end
+ end
+
+ context 'when revocation fails' do
+ before do
+ stub_request(:post, "https://oauth2.googleapis.com/revoke")
+ .to_return(status: 400, body: "", headers: {})
+ end
+
+ it 'calls revoke endpoint and redirects' do
+ post url
+
+ expect(request.session[GoogleApi::CloudPlatform::Client.session_key_for_token]).to be_nil
+ expect(response).to redirect_to(project_google_cloud_index_path(project))
+ expect(flash[:alert]).to eq('Google OAuth2 token revocation request failed')
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'revoke_oauth#create',
+ label: 'create',
+ property: 'failed',
+ project: project,
+ user: user
+ )
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb b/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb
index 0f243a6a7a9..4b32965e2b0 100644
--- a/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb
+++ b/spec/requests/projects/google_cloud/service_accounts_controller_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
let_it_be(:project) { create(:project, :public) }
- describe 'GET index' do
+ describe 'GET index', :snowplow do
let_it_be(:url) { "#{project_google_cloud_service_accounts_path(project)}" }
let(:user_guest) { create(:user) }
@@ -27,6 +27,14 @@ RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
get url
expect(response).to have_gitlab_http_status(:not_found)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: nil
+ )
end
it 'returns not found on POST request' do
@@ -42,6 +50,14 @@ RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
sign_in(unauthorized_member)
get url
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: unauthorized_member
+ )
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -52,6 +68,14 @@ RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
sign_in(unauthorized_member)
post url
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: unauthorized_member
+ )
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -80,34 +104,75 @@ RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
end
context 'and user has successfully completed the google oauth2 flow' do
- before do
- allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
- mock_service_account = Struct.new(:project_id, :unique_id, :email).new(123, 456, 'em@ai.l')
- allow(client).to receive(:validate_token).and_return(true)
- allow(client).to receive(:list_projects).and_return([{}, {}, {}])
- allow(client).to receive(:create_service_account).and_return(mock_service_account)
- allow(client).to receive(:create_service_account_key).and_return({})
- allow(client).to receive(:grant_service_account_roles)
+ context 'but the user does not have gcp projects' do
+ before do
+ allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
+ mock_service_account = Struct.new(:project_id, :unique_id, :email).new(123, 456, 'em@ai.l')
+ allow(client).to receive(:list_projects).and_return([])
+ allow(client).to receive(:validate_token).and_return(true)
+ allow(client).to receive(:create_service_account).and_return(mock_service_account)
+ allow(client).to receive(:create_service_account_key).and_return({})
+ allow(client).to receive(:grant_service_account_roles)
+ end
end
- end
- it 'returns success on GET' do
- authorized_members.each do |authorized_member|
- sign_in(authorized_member)
+ it 'renders no_gcp_projects' do
+ authorized_members.each do |authorized_member|
+ allow_next_instance_of(BranchesFinder) do |branches_finder|
+ allow(branches_finder).to receive(:execute).and_return([])
+ end
- get url
+ allow_next_instance_of(TagsFinder) do |branches_finder|
+ allow(branches_finder).to receive(:execute).and_return([])
+ end
+
+ sign_in(authorized_member)
- expect(response).to have_gitlab_http_status(:ok)
+ get url
+
+ expect(response).to render_template('projects/google_cloud/errors/no_gcp_projects')
+ end
end
end
- it 'returns success on POST' do
- authorized_members.each do |authorized_member|
- sign_in(authorized_member)
+ context 'user has three gcp_projects' do
+ before do
+ allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
+ mock_service_account = Struct.new(:project_id, :unique_id, :email).new(123, 456, 'em@ai.l')
+ allow(client).to receive(:list_projects).and_return([{}, {}, {}])
+ allow(client).to receive(:validate_token).and_return(true)
+ allow(client).to receive(:create_service_account).and_return(mock_service_account)
+ allow(client).to receive(:create_service_account_key).and_return({})
+ allow(client).to receive(:grant_service_account_roles)
+ end
+ end
- post url, params: { gcp_project: 'prj1', environment: 'env1' }
+ it 'returns success on GET' do
+ authorized_members.each do |authorized_member|
+ allow_next_instance_of(BranchesFinder) do |branches_finder|
+ allow(branches_finder).to receive(:execute).and_return([])
+ end
+
+ allow_next_instance_of(TagsFinder) do |branches_finder|
+ allow(branches_finder).to receive(:execute).and_return([])
+ end
+
+ sign_in(authorized_member)
+
+ get url
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ it 'returns success on POST' do
+ authorized_members.each do |authorized_member|
+ sign_in(authorized_member)
+
+ post url, params: { gcp_project: 'prj1', ref: 'env1' }
- expect(response).to redirect_to(project_google_cloud_index_path(project))
+ expect(response).to redirect_to(project_google_cloud_index_path(project))
+ end
end
end
end
diff --git a/spec/requests/projects/google_cloud_controller_spec.rb b/spec/requests/projects/google_cloud_controller_spec.rb
index 37682152994..d0814990989 100644
--- a/spec/requests/projects/google_cloud_controller_spec.rb
+++ b/spec/requests/projects/google_cloud_controller_spec.rb
@@ -8,7 +8,7 @@ MockGoogleOAuth2Credentials = Struct.new(:app_id, :app_secret)
RSpec.describe Projects::GoogleCloudController do
let_it_be(:project) { create(:project, :public) }
- describe 'GET index' do
+ describe 'GET index', :snowplow do
let_it_be(:url) { "#{project_google_cloud_index_path(project)}" }
context 'when a public request is made' do
@@ -16,6 +16,13 @@ RSpec.describe Projects::GoogleCloudController do
get url
expect(response).to have_gitlab_http_status(:not_found)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: nil)
end
end
@@ -29,6 +36,14 @@ RSpec.describe Projects::GoogleCloudController do
get url
expect(response).to have_gitlab_http_status(:not_found)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: user
+ )
end
end
@@ -42,6 +57,14 @@ RSpec.describe Projects::GoogleCloudController do
get url
expect(response).to have_gitlab_http_status(:not_found)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'admin_project_google_cloud!',
+ label: 'access_denied',
+ property: 'invalid_user',
+ project: project,
+ user: user
+ )
end
end
@@ -74,19 +97,26 @@ RSpec.describe Projects::GoogleCloudController do
let(:user) { project.creator }
context 'but gitlab instance is not configured for google oauth2' do
- before do
+ it 'returns forbidden' do
unconfigured_google_oauth2 = MockGoogleOAuth2Credentials.new('', '')
allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for)
.with('google_oauth2')
.and_return(unconfigured_google_oauth2)
- end
- it 'returns forbidden' do
sign_in(user)
get url
expect(response).to have_gitlab_http_status(:forbidden)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'google_oauth2_enabled!',
+ label: 'access_denied',
+ extra: { reason: 'google_oauth2_not_configured',
+ config: unconfigured_google_oauth2 },
+ project: project,
+ user: user
+ )
end
end
@@ -101,6 +131,46 @@ RSpec.describe Projects::GoogleCloudController do
get url
expect(response).to have_gitlab_http_status(:not_found)
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'feature_flag_enabled!',
+ label: 'access_denied',
+ property: 'feature_flag_not_enabled',
+ project: project,
+ user: user
+ )
+ end
+ end
+
+ context 'but google oauth2 token is not valid' do
+ it 'does not return revoke oauth url' do
+ allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
+ allow(client).to receive(:validate_token).and_return(false)
+ end
+
+ sign_in(user)
+
+ get url
+
+ expect(response).to be_successful
+ expect_snowplow_event(
+ category: 'Projects::GoogleCloud',
+ action: 'google_cloud#index',
+ label: 'index',
+ extra: {
+ screen: 'home',
+ serviceAccounts: [],
+ createServiceAccountUrl: project_google_cloud_service_accounts_path(project),
+ enableCloudRunUrl: project_google_cloud_deployments_cloud_run_path(project),
+ enableCloudStorageUrl: project_google_cloud_deployments_cloud_storage_path(project),
+ emptyIllustrationUrl: ActionController::Base.helpers.image_path('illustrations/pipelines_empty.svg'),
+ configureGcpRegionsUrl: project_google_cloud_gcp_regions_path(project),
+ gcpRegions: [],
+ revokeOauthUrl: nil
+ },
+ project: project,
+ user: user
+ )
end
end
end
diff --git a/spec/requests/projects/harbor/repositories_controller_spec.rb b/spec/requests/projects/harbor/repositories_controller_spec.rb
new file mode 100644
index 00000000000..cdb5a696d7e
--- /dev/null
+++ b/spec/requests/projects/harbor/repositories_controller_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::Harbor::RepositoriesController do
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ end
+ end
+
+ before do
+ stub_feature_flags(harbor_registry_integration: true)
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ describe 'GET #index' do
+ subject do
+ get project_harbor_registry_index_path(project)
+ response
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+
+ describe 'GET #show' do
+ subject do
+ get project_harbor_registry_path(project, 1)
+ response
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+end
diff --git a/spec/requests/projects/redirect_controller_spec.rb b/spec/requests/projects/redirect_controller_spec.rb
new file mode 100644
index 00000000000..3bbca3ca32b
--- /dev/null
+++ b/spec/requests/projects/redirect_controller_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe "Projects::RedirectController requests" do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:private_project) { create(:project, :private) }
+ let_it_be(:public_project) { create(:project, :public) }
+ let_it_be(:user) { create(:user) }
+
+ before_all do
+ private_project.add_developer(user)
+ end
+
+ describe 'GET redirect_from_id' do
+ where(:authenticated, :project, :is_found) do
+ true | ref(:private_project) | true
+ false | ref(:private_project) | false
+ true | ref(:public_project) | true
+ false | ref(:public_project) | true
+ true | build(:project, id: 0) | false
+ end
+
+ with_them do
+ before do
+ sign_in(user) if authenticated
+
+ get "/projects/#{project.id}"
+ end
+
+ if params[:is_found]
+ it 'redirects to the project page' do
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response).to redirect_to(project_path(project))
+ end
+ else
+ it 'gives 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
+ # This is a regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/351058
+ context 'with sourcegraph enabled' do
+ let_it_be(:sourcegraph_url) { 'https://sourcegraph.test' }
+
+ before do
+ allow(Gitlab::CurrentSettings).to receive(:sourcegraph_url).and_return(sourcegraph_url)
+ allow(Gitlab::CurrentSettings).to receive(:sourcegraph_enabled).and_return(true)
+
+ sign_in(user)
+ end
+
+ context 'with projects/:id route' do
+ subject { get "/projects/#{public_project.id}" }
+
+ it 'redirects successfully' do
+ subject
+
+ expect(response).to redirect_to(project_path(public_project))
+ end
+ end
+ end
+end