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/api/api_spec.rb112
-rw-r--r--spec/requests/api/bulk_imports_spec.rb9
-rw-r--r--spec/requests/api/ci/resource_groups_spec.rb95
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb6
-rw-r--r--spec/requests/api/ci/runner/runners_delete_spec.rb2
-rw-r--r--spec/requests/api/ci/runner/runners_post_spec.rb6
-rw-r--r--spec/requests/api/ci/runner/runners_verify_post_spec.rb2
-rw-r--r--spec/requests/api/ci/runners_reset_registration_token_spec.rb2
-rw-r--r--spec/requests/api/ci/runners_spec.rb98
-rw-r--r--spec/requests/api/ci/triggers_spec.rb6
-rw-r--r--spec/requests/api/container_repositories_spec.rb26
-rw-r--r--spec/requests/api/deployments_spec.rb10
-rw-r--r--spec/requests/api/environments_spec.rb7
-rw-r--r--spec/requests/api/error_tracking/client_keys_spec.rb (renamed from spec/requests/api/error_tracking_client_keys_spec.rb)2
-rw-r--r--spec/requests/api/error_tracking/collector_spec.rb (renamed from spec/requests/api/error_tracking_collector_spec.rb)2
-rw-r--r--spec/requests/api/error_tracking/project_settings_spec.rb (renamed from spec/requests/api/error_tracking_spec.rb)2
-rw-r--r--spec/requests/api/graphql/boards/board_list_issues_query_spec.rb16
-rw-r--r--spec/requests/api/graphql/boards/board_list_query_spec.rb98
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb6
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb88
-rw-r--r--spec/requests/api/graphql/ci/runners_spec.rb12
-rw-r--r--spec/requests/api/graphql/container_repository/container_repository_details_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/container_repositories_spec.rb9
-rw-r--r--spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb71
-rw-r--r--spec/requests/api/graphql/group/issues_spec.rb123
-rw-r--r--spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/clusters/agents/create_spec.rb42
-rw-r--r--spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb43
-rw-r--r--spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb65
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb2
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb8
-rw-r--r--spec/requests/api/graphql/project/cluster_agents_spec.rb108
-rw-r--r--spec/requests/api/graphql/project/container_repositories_spec.rb13
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb125
-rw-r--r--spec/requests/api/graphql/project/merge_request/pipelines_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb8
-rw-r--r--spec/requests/api/graphql/project/releases_spec.rb24
-rw-r--r--spec/requests/api/graphql/users_spec.rb12
-rw-r--r--spec/requests/api/group_container_repositories_spec.rb7
-rw-r--r--spec/requests/api/groups_spec.rb38
-rw-r--r--spec/requests/api/helm_packages_spec.rb18
-rw-r--r--spec/requests/api/integrations_spec.rb363
-rw-r--r--spec/requests/api/internal/base_spec.rb8
-rw-r--r--spec/requests/api/internal/kubernetes_spec.rb90
-rw-r--r--spec/requests/api/issues/issues_spec.rb38
-rw-r--r--spec/requests/api/issues/post_projects_issues_spec.rb157
-rw-r--r--spec/requests/api/maven_packages_spec.rb24
-rw-r--r--spec/requests/api/merge_requests_spec.rb12
-rw-r--r--spec/requests/api/package_files_spec.rb2
-rw-r--r--spec/requests/api/project_container_repositories_spec.rb5
-rw-r--r--spec/requests/api/project_export_spec.rb139
-rw-r--r--spec/requests/api/projects_spec.rb25
-rw-r--r--spec/requests/api/repositories_spec.rb12
-rw-r--r--spec/requests/api/services_spec.rb361
-rw-r--r--spec/requests/api/settings_spec.rb5
-rw-r--r--spec/requests/api/users_spec.rb27
-rw-r--r--spec/requests/groups/registry/repositories_controller_spec.rb1
-rw-r--r--spec/requests/import/url_controller_spec.rb45
-rw-r--r--spec/requests/projects/cluster_agents_controller_spec.rb40
-rw-r--r--spec/requests/projects/google_cloud_controller_spec.rb50
-rw-r--r--spec/requests/projects/merge_requests_discussions_spec.rb12
-rw-r--r--spec/requests/rack_attack_global_spec.rb274
63 files changed, 2387 insertions, 686 deletions
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index 81620fce448..95eb503c6bc 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -100,39 +100,105 @@ RSpec.describe API::API do
end
end
- context 'application context' do
- let_it_be(:project) { create(:project) }
+ describe 'logging', :aggregate_failures do
+ let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { project.owner }
- it 'logs all application context fields' do
- allow_any_instance_of(Gitlab::GrapeLogging::Loggers::ContextLogger).to receive(:parameters) do
- Gitlab::ApplicationContext.current.tap do |log_context|
- expect(log_context).to match('correlation_id' => an_instance_of(String),
- 'meta.caller_id' => 'GET /api/:version/projects/:id/issues',
- 'meta.remote_ip' => an_instance_of(String),
- 'meta.project' => project.full_path,
- 'meta.root_namespace' => project.namespace.full_path,
- 'meta.user' => user.username,
- 'meta.client_id' => an_instance_of(String),
- 'meta.feature_category' => 'issue_tracking')
+ context 'when the endpoint is handled by the application' do
+ context 'when the endpoint supports all possible fields' do
+ it 'logs all application context fields and the route' do
+ expect(described_class::LOG_FORMATTER).to receive(:call) do |_severity, _datetime, _, data|
+ expect(data.stringify_keys)
+ .to include('correlation_id' => an_instance_of(String),
+ 'meta.caller_id' => 'GET /api/:version/projects/:id/issues',
+ 'meta.remote_ip' => an_instance_of(String),
+ 'meta.project' => project.full_path,
+ 'meta.root_namespace' => project.namespace.full_path,
+ 'meta.user' => user.username,
+ 'meta.client_id' => a_string_matching(%r{\Auser/.+}),
+ 'meta.feature_category' => 'issue_tracking',
+ 'route' => '/api/:version/projects/:id/issues')
+ end
+
+ get(api("/projects/#{project.id}/issues", user))
+
+ expect(response).to have_gitlab_http_status(:ok)
end
end
- get(api("/projects/#{project.id}/issues", user))
+ it 'skips context fields that do not apply' do
+ expect(described_class::LOG_FORMATTER).to receive(:call) do |_severity, _datetime, _, data|
+ expect(data.stringify_keys)
+ .to include('correlation_id' => an_instance_of(String),
+ 'meta.caller_id' => 'GET /api/:version/broadcast_messages',
+ 'meta.remote_ip' => an_instance_of(String),
+ 'meta.client_id' => a_string_matching(%r{\Aip/.+}),
+ 'meta.feature_category' => 'navigation',
+ 'route' => '/api/:version/broadcast_messages')
+
+ expect(data.stringify_keys).not_to include('meta.project', 'meta.root_namespace', 'meta.user')
+ end
+
+ get(api('/broadcast_messages'))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when there is an unsupported media type' do
+ it 'logs the route and context metadata for the client' do
+ expect(described_class::LOG_FORMATTER).to receive(:call) do |_severity, _datetime, _, data|
+ expect(data.stringify_keys)
+ .to include('correlation_id' => an_instance_of(String),
+ 'meta.remote_ip' => an_instance_of(String),
+ 'meta.client_id' => a_string_matching(%r{\Aip/.+}),
+ 'route' => '/api/:version/users/:id')
+
+ expect(data.stringify_keys).not_to include('meta.caller_id', 'meta.feature_category', 'meta.user')
+ end
+
+ put(api("/users/#{user.id}", user), params: { 'name' => 'Test' }, headers: { 'Content-Type' => 'image/png' })
+
+ expect(response).to have_gitlab_http_status(:unsupported_media_type)
+ end
end
- it 'skips fields that do not apply' do
- allow_any_instance_of(Gitlab::GrapeLogging::Loggers::ContextLogger).to receive(:parameters) do
- Gitlab::ApplicationContext.current.tap do |log_context|
- expect(log_context).to match('correlation_id' => an_instance_of(String),
- 'meta.caller_id' => 'GET /api/:version/users',
- 'meta.remote_ip' => an_instance_of(String),
- 'meta.client_id' => an_instance_of(String),
- 'meta.feature_category' => 'users')
+ context 'when there is an OPTIONS request' do
+ it 'logs the route and context metadata for the client' do
+ expect(described_class::LOG_FORMATTER).to receive(:call) do |_severity, _datetime, _, data|
+ expect(data.stringify_keys)
+ .to include('correlation_id' => an_instance_of(String),
+ 'meta.remote_ip' => an_instance_of(String),
+ 'meta.client_id' => a_string_matching(%r{\Auser/.+}),
+ 'meta.user' => user.username,
+ 'meta.feature_category' => 'users',
+ 'route' => '/api/:version/users')
+
+ expect(data.stringify_keys).not_to include('meta.caller_id')
end
+
+ options(api('/users', user))
+
+ expect(response).to have_gitlab_http_status(:no_content)
end
+ end
- get(api('/users'))
+ context 'when the API version is not matched' do
+ it 'logs the route and context metadata for the client' do
+ expect(described_class::LOG_FORMATTER).to receive(:call) do |_severity, _datetime, _, data|
+ expect(data.stringify_keys)
+ .to include('correlation_id' => an_instance_of(String),
+ 'meta.remote_ip' => an_instance_of(String),
+ 'meta.client_id' => a_string_matching(%r{\Aip/.+}),
+ 'route' => '/api/:version/*path')
+
+ expect(data.stringify_keys).not_to include('meta.caller_id', 'meta.user')
+ end
+
+ get('/api/v4_or_is_it')
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
diff --git a/spec/requests/api/bulk_imports_spec.rb b/spec/requests/api/bulk_imports_spec.rb
index 1a28687c830..1602819a02e 100644
--- a/spec/requests/api/bulk_imports_spec.rb
+++ b/spec/requests/api/bulk_imports_spec.rb
@@ -21,6 +21,15 @@ RSpec.describe API::BulkImports do
end
describe 'POST /bulk_imports' do
+ before do
+ allow_next_instance_of(BulkImports::Clients::HTTP) do |instance|
+ allow(instance)
+ .to receive(:instance_version)
+ .and_return(
+ Gitlab::VersionInfo.new(::BulkImport::MIN_MAJOR_VERSION, ::BulkImport::MIN_MINOR_VERSION_FOR_PROJECT))
+ end
+ end
+
it 'starts a new migration' do
post api('/bulk_imports', user), params: {
configuration: {
diff --git a/spec/requests/api/ci/resource_groups_spec.rb b/spec/requests/api/ci/resource_groups_spec.rb
new file mode 100644
index 00000000000..f5b68557a0d
--- /dev/null
+++ b/spec/requests/api/ci/resource_groups_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Ci::ResourceGroups do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+ let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
+
+ let(:user) { developer }
+
+ describe 'GET /projects/:id/resource_groups/:key' do
+ subject { get api("/projects/#{project.id}/resource_groups/#{key}", user) }
+
+ let!(:resource_group) { create(:ci_resource_group, project: project) }
+ let(:key) { resource_group.key }
+
+ it 'returns a resource group', :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['id']).to eq(resource_group.id)
+ expect(json_response['key']).to eq(resource_group.key)
+ expect(json_response['process_mode']).to eq(resource_group.process_mode)
+ expect(Time.parse(json_response['created_at'])).to be_like_time(resource_group.created_at)
+ expect(Time.parse(json_response['updated_at'])).to be_like_time(resource_group.updated_at)
+ end
+
+ context 'when user is reporter' do
+ let(:user) { reporter }
+
+ it 'returns forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when there is no corresponding resource group' do
+ let(:key) { 'unknown' }
+
+ it 'returns not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/resource_groups/:key' do
+ subject { put api("/projects/#{project.id}/resource_groups/#{key}", user), params: params }
+
+ let!(:resource_group) { create(:ci_resource_group, project: project) }
+ let(:key) { resource_group.key }
+ let(:params) { { process_mode: :oldest_first } }
+
+ it 'changes the process mode of a resource group' do
+ expect { subject }
+ .to change { resource_group.reload.process_mode }.from('unordered').to('oldest_first')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['process_mode']).to eq('oldest_first')
+ end
+
+ context 'with invalid parameter' do
+ let(:params) { { process_mode: :unknown } }
+
+ it 'returns bad request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when user is reporter' do
+ let(:user) { reporter }
+
+ it 'returns forbidden' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'when there is no corresponding resource group' do
+ let(:key) { 'unknown' }
+
+ it 'returns not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
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 adac81ff6f4..c3fbef9be48 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -816,7 +816,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
subject { request_job(id: job.id) }
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let(:expected_params) { { user: user.username, project: project.full_path, client_id: "user/#{user.id}" } }
end
@@ -827,7 +827,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
context 'when the runner is of project type' do
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let(:expected_params) { { project: project.full_path, client_id: "runner/#{runner.id}" } }
end
@@ -841,7 +841,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let(:expected_params) { { root_namespace: group.full_path_components.first, client_id: "runner/#{runner.id}" } }
end
diff --git a/spec/requests/api/ci/runner/runners_delete_spec.rb b/spec/requests/api/ci/runner/runners_delete_spec.rb
index 6c6c465f161..9d1bae7cce8 100644
--- a/spec/requests/api/ci/runner/runners_delete_spec.rb
+++ b/spec/requests/api/ci/runner/runners_delete_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
let(:params) { { token: runner.token } }
end
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let(:expected_params) { { client_id: "runner/#{runner.id}" } }
end
end
diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb
index 17b988a60c5..b3a7d591c93 100644
--- a/spec/requests/api/ci/runner/runners_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_post_spec.rb
@@ -58,7 +58,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(runner).to be_instance_type
end
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
subject { request }
let(:expected_params) { { client_id: "runner/#{::Ci::Runner.first.id}" } }
@@ -84,7 +84,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(runner).to be_project_type
end
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
subject { request }
let(:expected_params) { { project: project.full_path, client_id: "runner/#{::Ci::Runner.first.id}" } }
@@ -190,7 +190,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(runner).to be_group_type
end
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
subject { request }
let(:expected_params) { { root_namespace: group.full_path_components.first, client_id: "runner/#{::Ci::Runner.first.id}" } }
diff --git a/spec/requests/api/ci/runner/runners_verify_post_spec.rb b/spec/requests/api/ci/runner/runners_verify_post_spec.rb
index c2e97446738..4680076acae 100644
--- a/spec/requests/api/ci/runner/runners_verify_post_spec.rb
+++ b/spec/requests/api/ci/runner/runners_verify_post_spec.rb
@@ -45,7 +45,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:ok)
end
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let(:expected_params) { { client_id: "runner/#{runner.id}" } }
end
end
diff --git a/spec/requests/api/ci/runners_reset_registration_token_spec.rb b/spec/requests/api/ci/runners_reset_registration_token_spec.rb
index 7623d3f1b17..df64c0bd22b 100644
--- a/spec/requests/api/ci/runners_reset_registration_token_spec.rb
+++ b/spec/requests/api/ci/runners_reset_registration_token_spec.rb
@@ -118,7 +118,7 @@ RSpec.describe API::Ci::Runners do
end
include_context 'when authorized', 'group' do
- let_it_be(:user) { create_default(:group_member, :maintainer, user: create(:user), group: group ).user }
+ let_it_be(:user) { create_default(:group_member, :owner, user: create(:user), group: group ).user }
def get_token
group.reload.runners_token
diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index 902938d7d02..6879dfc9572 100644
--- a/spec/requests/api/ci/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -291,6 +291,16 @@ RSpec.describe API::Ci::Runners do
end
end
+ context 'when the runner is a group runner' do
+ it "returns the runner's details" do
+ get api("/runners/#{group_runner_a.id}", admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['description']).to eq(group_runner_a.description)
+ expect(json_response['groups'].first['id']).to eq(group.id)
+ end
+ end
+
context "runner project's administrative user" do
context 'when runner is not shared' do
it "returns runner's details" do
@@ -600,6 +610,94 @@ RSpec.describe API::Ci::Runners do
end
end
+ describe 'POST /runners/:id/reset_authentication_token' do
+ context 'admin user' do
+ it 'resets shared runner authentication token' do
+ expect do
+ post api("/runners/#{shared_runner.id}/reset_authentication_token", admin)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response).to eq({ 'token' => shared_runner.reload.token })
+ end.to change { shared_runner.reload.token }
+ end
+
+ it 'returns 404 if runner does not exist' do
+ post api('/runners/0/reset_authentication_token', admin)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'authorized user' do
+ it 'does not reset project runner authentication token without access to it' do
+ expect do
+ post api("/runners/#{project_runner.id}/reset_authentication_token", user2)
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { project_runner.reload.token }
+ end
+
+ it 'resets project runner authentication token for owned project' do
+ expect do
+ post api("/runners/#{project_runner.id}/reset_authentication_token", user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response).to eq({ 'token' => project_runner.reload.token })
+ end.to change { project_runner.reload.token }
+ end
+
+ it 'does not reset group runner authentication token with guest access' do
+ expect do
+ post api("/runners/#{group_runner_a.id}/reset_authentication_token", group_guest)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { group_runner_a.reload.token }
+ end
+
+ it 'does not reset group runner authentication token with reporter access' do
+ expect do
+ post api("/runners/#{group_runner_a.id}/reset_authentication_token", group_reporter)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { group_runner_a.reload.token }
+ end
+
+ it 'does not reset group runner authentication token with developer access' do
+ expect do
+ post api("/runners/#{group_runner_a.id}/reset_authentication_token", group_developer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { group_runner_a.reload.token }
+ end
+
+ it 'does not reset group runner authentication token with maintainer access' do
+ expect do
+ post api("/runners/#{group_runner_a.id}/reset_authentication_token", group_maintainer)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { group_runner_a.reload.token }
+ end
+
+ it 'resets group runner authentication token with owner access' do
+ expect do
+ post api("/runners/#{group_runner_a.id}/reset_authentication_token", user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(json_response).to eq({ 'token' => group_runner_a.reload.token })
+ end.to change { group_runner_a.reload.token }
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'does not reset authentication token' do
+ expect do
+ post api("/runners/#{shared_runner.id}/reset_authentication_token")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end.not_to change { shared_runner.reload.token }
+ end
+ end
+ end
+
describe 'GET /runners/:id/jobs' do
let_it_be(:job_1) { create(:ci_build) }
let_it_be(:job_2) { create(:ci_build, :running, runner: shared_runner, project: project) }
diff --git a/spec/requests/api/ci/triggers_spec.rb b/spec/requests/api/ci/triggers_spec.rb
index 410e2ae405e..d270a16d28d 100644
--- a/spec/requests/api/ci/triggers_spec.rb
+++ b/spec/requests/api/ci/triggers_spec.rb
@@ -131,7 +131,7 @@ RSpec.describe API::Ci::Triggers do
let(:subject_proc) { proc { post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), params: { ref: 'refs/heads/other-branch' } } }
context 'when triggering a pipeline from a trigger token' do
- it_behaves_like 'storing arguments in the application context'
+ it_behaves_like 'storing arguments in the application context for the API'
it_behaves_like 'not executing any extra queries for the application context'
end
@@ -142,7 +142,7 @@ RSpec.describe API::Ci::Triggers do
context 'when other job is triggered by a user' do
let(:trigger_token) { create(:ci_build, :running, project: project, user: user).token }
- it_behaves_like 'storing arguments in the application context'
+ it_behaves_like 'storing arguments in the application context for the API'
it_behaves_like 'not executing any extra queries for the application context'
end
@@ -151,7 +151,7 @@ RSpec.describe API::Ci::Triggers do
let(:runner) { create(:ci_runner) }
let(:expected_params) { { client_id: "runner/#{runner.id}", project: project.full_path } }
- it_behaves_like 'storing arguments in the application context'
+ it_behaves_like 'storing arguments in the application context for the API'
it_behaves_like 'not executing any extra queries for the application context', 1
end
end
diff --git a/spec/requests/api/container_repositories_spec.rb b/spec/requests/api/container_repositories_spec.rb
index 8d7494ffce1..9809702467d 100644
--- a/spec/requests/api/container_repositories_spec.rb
+++ b/spec/requests/api/container_repositories_spec.rb
@@ -48,6 +48,19 @@ RSpec.describe API::ContainerRepositories do
expect(response).to match_response_schema('registry/repository')
end
+ context 'with a network error' do
+ before do
+ stub_container_registry_network_error(client_method: :repository_tags)
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/repository')
+ end
+ end
+
context 'with tags param' do
let(:url) { "/registry/repositories/#{repository.id}?tags=true" }
@@ -61,6 +74,19 @@ RSpec.describe API::ContainerRepositories do
expect(json_response['id']).to eq(repository.id)
expect(response.body).to include('tags')
end
+
+ context 'with a network error' do
+ before do
+ stub_container_registry_network_error(client_method: :repository_tags)
+ end
+
+ it 'returns a connection error message' do
+ 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
end
context 'with tags_count param' do
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index 38c96cd37af..69f7b54c277 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -376,6 +376,16 @@ RSpec.describe API::Deployments do
expect(json_response['status']).to eq('success')
end
+ it 'returns an error when an invalid status transition is detected' do
+ put(
+ api("/projects/#{project.id}/deployments/#{deploy.id}", user),
+ params: { status: 'running' }
+ )
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']['status']).to include(%Q{cannot transition via \"run\"})
+ end
+
it 'links merge requests when the deployment status changes to success', :sidekiq_inline do
mr = create(
:merge_request,
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index bc7bb7523c9..5fb24dc91a4 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe API::Environments do
get api("/projects/#{project.id}/environments", user)
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/environments')
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
@@ -167,6 +168,7 @@ RSpec.describe API::Environments do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep" }
expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['name']).to eq('mepmep')
expect(json_response['slug']).to eq('mepmep')
expect(json_response['external']).to be nil
@@ -212,6 +214,7 @@ RSpec.describe API::Environments do
params: { name: 'Mepmep', external_url: url }
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['name']).to eq('Mepmep')
expect(json_response['external_url']).to eq(url)
end
@@ -250,7 +253,7 @@ RSpec.describe API::Environments do
expect(response).to have_gitlab_http_status(:forbidden)
end
- it 'returns a 200 for stopped environment' do
+ it 'returns a 204 for stopped environment' do
environment.stop
delete api("/projects/#{project.id}/environments/#{environment.id}", user)
@@ -294,6 +297,7 @@ RSpec.describe API::Environments do
it 'returns a 200' do
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/environment')
end
it 'actually stops the environment' do
@@ -327,6 +331,7 @@ RSpec.describe API::Environments do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/environment')
+ expect(json_response['last_deployment']).to be_present
end
end
diff --git a/spec/requests/api/error_tracking_client_keys_spec.rb b/spec/requests/api/error_tracking/client_keys_spec.rb
index 886ec5ade3d..00c1e8799e6 100644
--- a/spec/requests/api/error_tracking_client_keys_spec.rb
+++ b/spec/requests/api/error_tracking/client_keys_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe API::ErrorTrackingClientKeys do
+RSpec.describe API::ErrorTracking::ClientKeys do
let_it_be(:guest) { create(:user) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:setting) { create(:project_error_tracking_setting) }
diff --git a/spec/requests/api/error_tracking_collector_spec.rb b/spec/requests/api/error_tracking/collector_spec.rb
index 35d3ea01f87..7acadeb1287 100644
--- a/spec/requests/api/error_tracking_collector_spec.rb
+++ b/spec/requests/api/error_tracking/collector_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe API::ErrorTrackingCollector do
+RSpec.describe API::ErrorTracking::Collector do
let_it_be(:project) { create(:project, :private) }
let_it_be(:setting) { create(:project_error_tracking_setting, :integrated, project: project) }
let_it_be(:client_key) { create(:error_tracking_client_key, project: project) }
diff --git a/spec/requests/api/error_tracking_spec.rb b/spec/requests/api/error_tracking/project_settings_spec.rb
index ec9a3378acc..161e4f01ea5 100644
--- a/spec/requests/api/error_tracking_spec.rb
+++ b/spec/requests/api/error_tracking/project_settings_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe API::ErrorTracking do
+RSpec.describe API::ErrorTracking::ProjectSettings do
let_it_be(:user) { create(:user) }
let(:setting) { create(:project_error_tracking_setting) }
diff --git a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
index 008241b8055..241c658441b 100644
--- a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe 'get board lists' do
nodes {
lists {
nodes {
- issues(filters: {labelName: "#{label2.title}"}) {
+ issues(filters: {labelName: "#{label2.title}"}, first: 3) {
count
nodes {
#{all_graphql_fields_for('issues'.classify)}
@@ -44,6 +44,10 @@ RSpec.describe 'get board lists' do
)
end
+ def issue_id
+ issues_data.map { |i| i['id'] }
+ end
+
def issue_titles
issues_data.map { |i| i['title'] }
end
@@ -60,6 +64,7 @@ RSpec.describe 'get board lists' do
let!(:issue3) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
let!(:issue4) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
let!(:issue5) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) }
+ let!(:issue6) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
context 'when the user does not have access to the board' do
it 'returns nil' do
@@ -72,14 +77,19 @@ RSpec.describe 'get board lists' do
context 'when user can read the board' do
before do
board_parent.add_reporter(user)
+ post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user)
end
it 'can access the issues', :aggregate_failures do
- post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user)
-
+ # ties for relative positions are broken by id in ascending order by default
expect(issue_titles).to eq([issue2.title, issue1.title, issue3.title])
expect(issue_relative_positions).not_to include(nil)
end
+
+ it 'does not set the relative positions of the issues not being returned', :aggregate_failures do
+ expect(issue_id).not_to include(issue6.id)
+ expect(issue3.relative_position).to be_nil
+ end
end
end
diff --git a/spec/requests/api/graphql/boards/board_list_query_spec.rb b/spec/requests/api/graphql/boards/board_list_query_spec.rb
new file mode 100644
index 00000000000..dec7ca715f2
--- /dev/null
+++ b/spec/requests/api/graphql/boards/board_list_query_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Querying a Board list' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:board) { create(:board, resource_parent: project) }
+ let_it_be(:label) { create(:label, project: project, name: 'foo') }
+ let_it_be(:list) { create(:list, board: board, label: label) }
+ let_it_be(:issue1) { create(:issue, project: project, labels: [label]) }
+ let_it_be(:issue2) { create(:issue, project: project, labels: [label], assignees: [current_user]) }
+
+ let(:filters) { {} }
+ let(:query) do
+ graphql_query_for(
+ :board_list,
+ { id: list.to_global_id.to_s, issueFilters: filters },
+ %w[title issuesCount]
+ )
+ end
+
+ subject { graphql_data['boardList'] }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ context 'when the user has access to the list' do
+ before_all do
+ project.add_guest(current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it { is_expected.to include({ 'issuesCount' => 2, 'title' => list.title }) }
+
+ context 'with matching issue filters' do
+ let(:filters) { { assigneeUsername: current_user.username } }
+
+ it 'filters issues metadata' do
+ is_expected.to include({ 'issuesCount' => 1, 'title' => list.title })
+ end
+ end
+
+ context 'with unmatching issue filters' do
+ let(:filters) { { assigneeUsername: 'foo' } }
+
+ it 'filters issues metadata' do
+ is_expected.to include({ 'issuesCount' => 0, 'title' => list.title })
+ end
+ end
+ end
+
+ context 'when the user does not have access to the list' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'when ID argument is missing' do
+ let(:query) do
+ graphql_query_for('boardList', {}, 'title')
+ end
+
+ it 'raises an exception' do
+ expect(graphql_errors).to include(a_hash_including('message' => "Field 'boardList' is missing required arguments: id"))
+ end
+ end
+
+ context 'when list ID is not found' do
+ let(:query) do
+ graphql_query_for('boardList', { id: "gid://gitlab/List/#{non_existing_record_id}" }, 'title')
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ it 'does not have an N+1 performance issue' do
+ a, b = create_list(:list, 2, board: board)
+ ctx = { current_user: current_user }
+ project.add_guest(current_user)
+
+ baseline = graphql_query_for(:board_list, { id: global_id_of(a) }, 'title')
+ query = <<~GQL
+ query {
+ a: #{query_graphql_field(:board_list, { id: global_id_of(a) }, 'title')}
+ b: #{query_graphql_field(:board_list, { id: global_id_of(b) }, 'title')}
+ }
+ GQL
+
+ control = ActiveRecord::QueryRecorder.new do
+ run_with_clean_state(baseline, context: ctx)
+ end
+
+ expect { run_with_clean_state(query, context: ctx) }.not_to exceed_query_limit(control)
+ end
+end
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
index 2d52cddcacc..ace8c59e82d 100644
--- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -92,9 +92,9 @@ RSpec.describe 'get board lists' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { }
- let(:first_param) { 2 }
- let(:expected_results) { lists.map { |list| global_id_of(list) } }
+ let(:sort_param) { }
+ let(:first_param) { 2 }
+ let(:all_records) { lists.map { |list| global_id_of(list) } }
end
end
end
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 74547196445..ab53ff654e9 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe 'Query.runner(id)' do
include GraphqlHelpers
let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:group) { create(:group) }
let_it_be(:active_instance_runner) do
create(:ci_runner, :instance, description: 'Runner 1', contacted_at: 2.hours.ago,
@@ -18,12 +19,20 @@ RSpec.describe 'Query.runner(id)' do
version: 'adfe157', revision: 'b', ip_address: '10.10.10.10', access_level: 1, run_untagged: true)
end
+ let_it_be(:active_group_runner) do
+ create(:ci_runner, :group, groups: [group], description: 'Group runner 1', contacted_at: 2.hours.ago,
+ active: true, version: 'adfe156', revision: 'a', locked: true, ip_address: '127.0.0.1', maximum_timeout: 600,
+ access_level: 0, tag_list: %w[tag1 tag2], run_untagged: true)
+ end
+
def get_runner(id)
case id
when :active_instance_runner
active_instance_runner
when :inactive_instance_runner
inactive_instance_runner
+ when :active_group_runner
+ active_group_runner
end
end
@@ -61,7 +70,39 @@ RSpec.describe 'Query.runner(id)' do
'ipAddress' => runner.ip_address,
'runnerType' => runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE',
'jobCount' => 0,
- 'projectCount' => nil
+ 'projectCount' => nil,
+ 'adminUrl' => "http://localhost/admin/runners/#{runner.id}",
+ 'userPermissions' => {
+ 'readRunner' => true,
+ 'updateRunner' => true,
+ 'deleteRunner' => true
+ }
+ )
+ expect(runner_data['tagList']).to match_array runner.tag_list
+ end
+ end
+
+ shared_examples 'retrieval with no admin url' do |runner_id|
+ let(:query) do
+ wrap_fields(query_graphql_path(query_path, all_graphql_fields_for('CiRunner')))
+ end
+
+ let(:query_path) do
+ [
+ [:runner, { id: get_runner(runner_id).to_global_id.to_s }]
+ ]
+ end
+
+ it 'retrieves expected fields' do
+ post_graphql(query, current_user: user)
+
+ runner_data = graphql_data_at(:runner)
+ expect(runner_data).not_to be_nil
+
+ runner = get_runner(runner_id)
+ expect(runner_data).to match a_hash_including(
+ 'id' => "gid://gitlab/Ci::Runner/#{runner.id}",
+ 'adminUrl' => nil
)
expect(runner_data['tagList']).to match_array runner.tag_list
end
@@ -147,6 +188,39 @@ 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' => "gid://gitlab/Ci::Runner/#{active_group_runner.id}"
+ }
+ )
+ ]
+ end
+ end
+
describe 'for multiple runners' do
let_it_be(:project1) { create(:project, :test_repo) }
let_it_be(:project2) { create(:project, :test_repo) }
@@ -176,7 +250,7 @@ RSpec.describe 'Query.runner(id)' do
end
before do
- project_runner2.projects.clear
+ project_runner2.runner_projects.clear
post_graphql(query, current_user: user)
end
@@ -205,6 +279,16 @@ RSpec.describe 'Query.runner(id)' do
it_behaves_like 'retrieval by unauthorized user', :active_instance_runner
end
+ describe 'by non-admin user' do
+ let(:user) { create(:user) }
+
+ before do
+ group.add_user(user, Gitlab::Access::OWNER)
+ end
+
+ it_behaves_like 'retrieval with no admin url', :active_group_runner
+ end
+
describe 'by unauthenticated user' do
let(:user) { nil }
diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb
index 778fe5b129e..51a07e60e15 100644
--- a/spec/requests/api/graphql/ci/runners_spec.rb
+++ b/spec/requests/api/graphql/ci/runners_spec.rb
@@ -95,9 +95,9 @@ RSpec.describe 'Query.runners' do
let(:ordered_runners) { runners.sort_by(&:contacted_at) }
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CONTACTED_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { ordered_runners.map(&:id) }
+ let(:sort_param) { :CONTACTED_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { ordered_runners.map(&:id) }
end
end
@@ -105,9 +105,9 @@ RSpec.describe 'Query.runners' do
let(:ordered_runners) { runners.sort_by(&:created_at).reverse }
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { ordered_runners.map(&:id) }
+ let(:sort_param) { :CREATED_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { ordered_runners.map(&:id) }
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 356e1e11def..d93afcc0f33 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
@@ -153,4 +153,6 @@ RSpec.describe 'container repository details' do
end
end
end
+
+ it_behaves_like 'handling graphql network errors with the container registry'
end
diff --git a/spec/requests/api/graphql/group/container_repositories_spec.rb b/spec/requests/api/graphql/group/container_repositories_spec.rb
index 939d7791d92..be0b866af4a 100644
--- a/spec/requests/api/graphql/group/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/group/container_repositories_spec.rb
@@ -14,11 +14,12 @@ RSpec.describe 'getting container repositories in a group' do
let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten }
let_it_be(:container_expiration_policy) { project.container_expiration_policy }
+ let(:excluded_fields) { [] }
let(:container_repositories_fields) do
<<~GQL
edges {
node {
- #{all_graphql_fields_for('container_repositories'.classify, max_depth: 1)}
+ #{all_graphql_fields_for('container_repositories'.classify, max_depth: 1, excluded: excluded_fields)}
}
}
GQL
@@ -152,6 +153,12 @@ RSpec.describe 'getting container repositories in a group' do
end
end
+ it_behaves_like 'handling graphql network errors with the container registry'
+
+ it_behaves_like 'not hitting graphql network errors with the container registry' do
+ let(:excluded_fields) { %w[tags tagsCount] }
+ end
+
it 'returns the total count of container repositories' do
subject
diff --git a/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb
index c5c6d85d1e6..de3dbc5c324 100644
--- a/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb
+++ b/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb
@@ -33,46 +33,59 @@ RSpec.describe 'getting dependency proxy settings for a group' do
before do
stub_config(dependency_proxy: { enabled: true })
- group.create_dependency_proxy_setting!(enabled: true)
end
subject { post_graphql(query, current_user: user, variables: variables) }
- it_behaves_like 'a working graphql query' do
- before do
- subject
- end
- end
-
- context 'with different permissions' do
- where(:group_visibility, :role, :access_granted) do
- :private | :maintainer | true
- :private | :developer | true
- :private | :reporter | true
- :private | :guest | true
- :private | :anonymous | false
- :public | :maintainer | true
- :public | :developer | true
- :public | :reporter | true
- :public | :guest | true
- :public | :anonymous | false
+ shared_examples 'dependency proxy group setting query' do
+ it_behaves_like 'a working graphql query' do
+ before do
+ subject
+ end
end
- with_them do
- before do
- group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false))
- group.add_user(user, role) unless role == :anonymous
+ context 'with different permissions' do
+ where(:group_visibility, :role, :access_granted) do
+ :private | :maintainer | true
+ :private | :developer | true
+ :private | :reporter | true
+ :private | :guest | true
+ :private | :anonymous | false
+ :public | :maintainer | true
+ :public | :developer | true
+ :public | :reporter | true
+ :public | :guest | true
+ :public | :anonymous | false
end
- it 'return the proper response' do
- subject
+ with_them do
+ before do
+ group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false))
+ group.add_user(user, role) unless role == :anonymous
+ end
+
+ it 'return the proper response' do
+ subject
- if access_granted
- expect(dependency_proxy_group_setting_response).to eq('enabled' => true)
- else
- expect(dependency_proxy_group_setting_response).to be_blank
+ if access_granted
+ expect(dependency_proxy_group_setting_response).to eq('enabled' => true)
+ else
+ expect(dependency_proxy_group_setting_response).to be_blank
+ end
end
end
end
end
+
+ context 'with the settings model created' do
+ before do
+ group.create_dependency_proxy_setting!(enabled: true)
+ end
+
+ it_behaves_like 'dependency proxy group setting query'
+ end
+
+ context 'without the settings model created' do
+ it_behaves_like 'dependency proxy group setting query'
+ end
end
diff --git a/spec/requests/api/graphql/group/issues_spec.rb b/spec/requests/api/graphql/group/issues_spec.rb
new file mode 100644
index 00000000000..332bf242e9c
--- /dev/null
+++ b/spec/requests/api/graphql/group/issues_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting an issue list for a group' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group1) { create(:group) }
+ let_it_be(:group2) { create(:group) }
+ let_it_be(:project1) { create(:project, :public, group: group1) }
+ let_it_be(:project2) { create(:project, :private, group: group1) }
+ let_it_be(:project3) { create(:project, :public, group: group2) }
+ let_it_be(:issue1) { create(:issue, project: project1) }
+ let_it_be(:issue2) { create(:issue, project: project2) }
+ let_it_be(:issue3) { create(:issue, project: project3) }
+
+ let(:issue1_gid) { issue1.to_global_id.to_s }
+ let(:issue2_gid) { issue2.to_global_id.to_s }
+ let(:issues_data) { graphql_data['group']['issues']['edges'] }
+ let(:issue_filter_params) { {} }
+
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ #{all_graphql_fields_for('issues'.classify)}
+ }
+ }
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'group',
+ { 'fullPath' => group1.full_path },
+ query_graphql_field('issues', issue_filter_params, fields)
+ )
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ 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) }
+ let_it_be(:confidential_issue3) { create(:issue, :confidential, project: project3) }
+
+ let(:confidential_issue1_gid) { confidential_issue1.to_global_id.to_s }
+ let(:confidential_issue2_gid) { confidential_issue2.to_global_id.to_s }
+
+ context 'when the user cannot see confidential issues' do
+ before do
+ group1.add_guest(current_user)
+ end
+
+ it 'returns issues without confidential issues for the group' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid)
+ end
+
+ context 'filtering for confidential issues' do
+ let(:issue_filter_params) { { confidential: true } }
+
+ it 'returns no issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to be_empty
+ end
+ end
+
+ context 'filtering for non-confidential issues' do
+ let(:issue_filter_params) { { confidential: false } }
+
+ it 'returns correctly filtered issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid)
+ end
+ end
+ end
+
+ context 'when the user can see confidential issues' do
+ before do
+ group1.add_developer(current_user)
+ end
+
+ it 'returns issues with confidential issues for the group' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid, confidential_issue1_gid, confidential_issue2_gid)
+ end
+
+ context 'filtering for confidential issues' do
+ let(:issue_filter_params) { { confidential: true } }
+
+ it 'returns correctly filtered issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(confidential_issue1_gid, confidential_issue2_gid)
+ end
+ end
+
+ context 'filtering for non-confidential issues' do
+ let(:issue_filter_params) { { confidential: false } }
+
+ it 'returns correctly filtered issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid)
+ end
+ end
+ end
+ end
+
+ def issues_ids
+ graphql_dig_at(issues_data, :node, :id)
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb
index 07b05ead651..0fd8fdc3f59 100644
--- a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb
+++ b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb
@@ -89,7 +89,7 @@ RSpec.describe 'RunnersRegistrationTokenReset' do
end
include_context 'when authorized', 'group' do
- let_it_be(:user) { create_default(:group_member, :maintainer, user: create(:user), group: group ).user }
+ let_it_be(:user) { create_default(:group_member, :owner, user: create(:user), group: group ).user }
def get_token
group.reload.runners_token
diff --git a/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb b/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb
new file mode 100644
index 00000000000..aac8eb22771
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/clusters/agent_tokens/agent_tokens/create_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Create a new cluster agent token' do
+ include GraphqlHelpers
+
+ let_it_be(:cluster_agent) { create(:cluster_agent) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:description) { 'create token' }
+ let(:name) { 'token name' }
+ let(:mutation) do
+ graphql_mutation(
+ :cluster_agent_token_create,
+ { cluster_agent_id: cluster_agent.to_global_id.to_s, description: description, name: name }
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:cluster_agent_token_create)
+ end
+
+ context 'without user permissions' do
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ["The resource that you are attempting to access does not exist "\
+ "or you don't have permission to perform this action"]
+
+ it 'does not create a token' do
+ expect { post_graphql_mutation(mutation, current_user: current_user) }.not_to change(Clusters::AgentToken, :count)
+ end
+ end
+
+ context 'with project permissions' do
+ before do
+ cluster_agent.project.add_maintainer(current_user)
+ end
+
+ it 'creates a new token', :aggregate_failures do
+ expect { post_graphql_mutation(mutation, current_user: current_user) }.to change { Clusters::AgentToken.count }.by(1)
+ expect(mutation_response['errors']).to eq([])
+ end
+
+ it 'returns token information', :aggregate_failures do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['secret']).not_to be_nil
+ expect(mutation_response.dig('token', 'description')).to eq(description)
+ expect(mutation_response.dig('token', 'name')).to eq(name)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/clusters/agents/create_spec.rb b/spec/requests/api/graphql/mutations/clusters/agents/create_spec.rb
new file mode 100644
index 00000000000..c2ef2362d66
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/clusters/agents/create_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Create a new cluster agent' do
+ include GraphqlHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:project_name) { 'agent-test' }
+ let(:current_user) { create(:user) }
+
+ let(:mutation) do
+ graphql_mutation(
+ :create_cluster_agent,
+ { project_path: project.full_path, name: project_name }
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_cluster_agent)
+ end
+
+ context 'without project permissions' do
+ it_behaves_like 'a mutation that returns a top-level access error'
+
+ it 'does not create cluster agent' do
+ expect { post_graphql_mutation(mutation, current_user: current_user) }.not_to change(Clusters::Agent, :count)
+ end
+ end
+
+ context 'with user permissions' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ it 'creates a new cluster agent', :aggregate_failures do
+ expect { post_graphql_mutation(mutation, current_user: current_user) }.to change { Clusters::Agent.count }.by(1)
+ expect(mutation_response.dig('clusterAgent', 'name')).to eq(project_name)
+ expect(mutation_response['errors']).to eq([])
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb
new file mode 100644
index 00000000000..5f6822223ca
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/clusters/agents/delete_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Delete a cluster agent' do
+ include GraphqlHelpers
+
+ let(:cluster_agent) { create(:cluster_agent) }
+ let(:project) { cluster_agent.project }
+ let(:current_user) { create(:user) }
+
+ let(:mutation) do
+ graphql_mutation(
+ :cluster_agent_delete,
+ { id: cluster_agent.to_global_id.uri }
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:cluster_agent_delete)
+ end
+
+ context 'without project permissions' do
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist '\
+ 'or you don\'t have permission to perform this action']
+
+ it 'does not delete cluster agent' do
+ expect { cluster_agent.reload }.not_to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'with project permissions' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ it 'deletes a cluster agent', :aggregate_failures do
+ expect { post_graphql_mutation(mutation, current_user: current_user) }.to change { Clusters::Agent.count }.by(-1)
+ expect(mutation_response['errors']).to eq([])
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb b/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb
new file mode 100644
index 00000000000..f05bf23ad27
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/dependency_proxy/group_settings/update_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Updating the dependency proxy group settings' do
+ include GraphqlHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:user) { create(:user) }
+
+ let(:params) do
+ {
+ group_path: group.full_path,
+ enabled: false
+ }
+ end
+
+ let(:mutation) do
+ graphql_mutation(:update_dependency_proxy_settings, params) do
+ <<~QL
+ dependencyProxySetting {
+ enabled
+ }
+ errors
+ QL
+ end
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:update_dependency_proxy_settings) }
+ let(:group_settings) { mutation_response['dependencyProxySetting'] }
+
+ before do
+ stub_config(dependency_proxy: { enabled: true })
+ end
+
+ describe 'post graphql mutation' do
+ subject { post_graphql_mutation(mutation, current_user: user) }
+
+ let_it_be_with_reload(:group) { create(:group) }
+ let_it_be_with_reload(:group_settings) { create(:dependency_proxy_group_setting, group: group) }
+
+ context 'without permission' do
+ it 'returns no response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response).to be_nil
+ end
+ end
+
+ context 'with permission' do
+ before do
+ group.add_developer(user)
+ end
+
+ it 'returns the updated dependency proxy settings', :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to be_empty
+ expect(group_settings[:enabled]).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
index dec9afd1310..608b36e4f15 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -115,7 +115,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled do
context 'when passing append as true' do
let(:mode) { Types::MutationOperationModeEnum.enum[:append] }
let(:input) { { assignee_usernames: [assignee2.username], operation_mode: mode } }
- let(:db_query_limit) { 21 }
+ let(:db_query_limit) { 22 }
before do
# In CE, APPEND is a NOOP as you can't have multiple assignees
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 414847c9c93..d5410f1a7cb 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -106,10 +106,10 @@ RSpec.describe 'getting projects' do
context 'when sorting by similarity' do
it_behaves_like 'sorted paginated query' do
- let(:node_path) { %w[name] }
- let(:sort_param) { :SIMILARITY }
- let(:first_param) { 2 }
- let(:expected_results) { [project_3.name, project_2.name, project_4.name] }
+ let(:node_path) { %w[name] }
+ let(:sort_param) { :SIMILARITY }
+ let(:first_param) { 2 }
+ let(:all_records) { [project_3.name, project_2.name, project_4.name] }
end
end
end
diff --git a/spec/requests/api/graphql/project/cluster_agents_spec.rb b/spec/requests/api/graphql/project/cluster_agents_spec.rb
new file mode 100644
index 00000000000..dc7254dd552
--- /dev/null
+++ b/spec/requests/api/graphql/project/cluster_agents_spec.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Project.cluster_agents' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:current_user) { create(:user, maintainer_projects: [project]) }
+ let_it_be(:agents) { create_list(:cluster_agent, 5, project: project) }
+
+ let(:first) { var('Int') }
+ let(:cluster_agents_fields) { nil }
+ let(:project_fields) do
+ query_nodes(:cluster_agents, cluster_agents_fields, args: { first: first }, max_depth: 3)
+ end
+
+ let(:query) do
+ args = { full_path: project.full_path }
+
+ with_signature([first], graphql_query_for(:project, args, project_fields))
+ end
+
+ before do
+ allow(Gitlab::Kas::Client).to receive(:new).and_return(double(get_connected_agents: []))
+ end
+
+ it 'can retrieve cluster agents' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data_at(:project, :cluster_agents, :nodes)).to match_array(
+ agents.map { |agent| a_hash_including('id' => global_id_of(agent)) }
+ )
+ end
+
+ context 'selecting page info' do
+ let(:project_fields) do
+ query_nodes(:cluster_agents, args: { first: first }, include_pagination_info: true)
+ end
+
+ it 'can paginate cluster agents' do
+ post_graphql(query, current_user: current_user, variables: first.with(2))
+
+ expect(graphql_data_at(:project, :cluster_agents, :page_info)).to include(
+ 'hasNextPage' => be_truthy,
+ 'hasPreviousPage' => be_falsey
+ )
+ expect(graphql_data_at(:project, :cluster_agents, :nodes)).to have_attributes(size: 2)
+ end
+ end
+
+ context 'selecting tokens' do
+ let_it_be(:token_1) { create(:cluster_agent_token, agent: agents.second) }
+ let_it_be(:token_2) { create(:cluster_agent_token, agent: agents.second, last_used_at: 3.days.ago) }
+ let_it_be(:token_3) { create(:cluster_agent_token, agent: agents.second, last_used_at: 2.days.ago) }
+
+ let(:cluster_agents_fields) { [:id, query_nodes(:tokens, of: 'ClusterAgentToken')] }
+
+ it 'can select tokens in last_used_at order' do
+ post_graphql(query, current_user: current_user)
+
+ tokens = graphql_data_at(:project, :cluster_agents, :nodes, :tokens, :nodes)
+
+ expect(tokens).to match([
+ a_hash_including('id' => global_id_of(token_3)),
+ a_hash_including('id' => global_id_of(token_2)),
+ a_hash_including('id' => global_id_of(token_1))
+ ])
+ end
+
+ it 'does not suffer from N+1 performance issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect do
+ post_graphql(query, current_user: current_user)
+ end.to issue_same_number_of_queries_as { post_graphql(query, current_user: current_user, variables: [first.with(1)]) }
+ end
+ end
+
+ context 'selecting connections' do
+ let(:agent_meta) { double(version: '1', commit_id: 'abc', pod_namespace: 'namespace', pod_name: 'pod') }
+ let(:connected_agent) { double(agent_id: agents.first.id, connected_at: 123456, connection_id: 1, agent_meta: agent_meta) }
+
+ let(:metadata_fields) { query_graphql_field(:metadata, {}, [:version, :commit, :pod_namespace, :pod_name], 'AgentMetadata') }
+ let(:cluster_agents_fields) { [:id, query_nodes(:connections, [:connection_id, :connected_at, metadata_fields])] }
+
+ before do
+ allow(Gitlab::Kas::Client).to receive(:new).and_return(double(get_connected_agents: [connected_agent]))
+ end
+
+ it 'can retrieve connections and agent metadata' do
+ post_graphql(query, current_user: current_user)
+
+ connection = graphql_data_at(:project, :cluster_agents, :nodes, :connections, :nodes).first
+
+ expect(connection).to include({
+ 'connectionId' => connected_agent.connection_id.to_s,
+ 'connectedAt' => Time.at(connected_agent.connected_at),
+ 'metadata' => {
+ 'version' => agent_meta.version,
+ 'commit' => agent_meta.commit_id,
+ 'podNamespace' => agent_meta.pod_namespace,
+ 'podName' => agent_meta.pod_name
+ }
+ })
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb
index 3ad56223b61..692143b2215 100644
--- a/spec/requests/api/graphql/project/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/project/container_repositories_spec.rb
@@ -12,11 +12,12 @@ RSpec.describe 'getting container repositories in a project' do
let_it_be(:container_repositories) { [container_repository, container_repositories_delete_scheduled, container_repositories_delete_failed].flatten }
let_it_be(:container_expiration_policy) { project.container_expiration_policy }
+ let(:excluded_fields) { %w[pipeline jobs] }
let(:container_repositories_fields) do
<<~GQL
edges {
node {
- #{all_graphql_fields_for('container_repositories'.classify, excluded: %w(pipeline jobs))}
+ #{all_graphql_fields_for('container_repositories'.classify, excluded: excluded_fields)}
}
}
GQL
@@ -151,6 +152,12 @@ RSpec.describe 'getting container repositories in a project' do
end
end
+ it_behaves_like 'handling graphql network errors with the container registry'
+
+ it_behaves_like 'not hitting graphql network errors with the container registry' do
+ let(:excluded_fields) { %w[pipeline jobs tags tagsCount] }
+ end
+
it 'returns the total count of container repositories' do
subject
@@ -190,7 +197,7 @@ RSpec.describe 'getting container repositories in a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :NAME_ASC }
let(:first_param) { 2 }
- let(:expected_results) { [container_repository2.name, container_repository1.name, container_repository4.name, container_repository3.name, container_repository5.name] }
+ let(:all_records) { [container_repository2.name, container_repository1.name, container_repository4.name, container_repository3.name, container_repository5.name] }
end
end
@@ -198,7 +205,7 @@ RSpec.describe 'getting container repositories in a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :NAME_DESC }
let(:first_param) { 2 }
- let(:expected_results) { [container_repository5.name, container_repository3.name, container_repository4.name, container_repository1.name, container_repository2.name] }
+ let(:all_records) { [container_repository5.name, container_repository3.name, container_repository4.name, container_repository1.name, container_repository2.name] }
end
end
end
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index c6b4d82bf15..1c6d6ce4707 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -5,12 +5,15 @@ require 'spec_helper'
RSpec.describe 'getting an issue list for a project' do
include GraphqlHelpers
- let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, :repository, :public, group: group) }
let_it_be(:current_user) { create(:user) }
let_it_be(:issue_a, reload: true) { create(:issue, project: project, discussion_locked: true) }
let_it_be(:issue_b, reload: true) { create(:issue, :with_alert, project: project) }
let_it_be(:issues, reload: true) { [issue_a, issue_b] }
+ let(:issue_a_gid) { issue_a.to_global_id.to_s }
+ let(:issue_b_gid) { issue_b.to_global_id.to_s }
let(:issues_data) { graphql_data['project']['issues']['edges'] }
let(:issue_filter_params) { {} }
@@ -66,9 +69,6 @@ RSpec.describe 'getting an issue list for a project' do
let_it_be(:upvote_award) { create(:award_emoji, :upvote, user: current_user, awardable: issue_a) }
- let(:issue_a_gid) { issue_a.to_global_id.to_s }
- let(:issue_b_gid) { issue_b.to_global_id.to_s }
-
where(:value, :gids) do
'thumbsup' | lazy { [issue_a_gid] }
'ANY' | lazy { [issue_a_gid] }
@@ -84,7 +84,7 @@ RSpec.describe 'getting an issue list for a project' do
it 'returns correctly filtered issues' do
post_graphql(query, current_user: current_user)
- expect(graphql_dig_at(issues_data, :node, :id)).to eq(gids)
+ expect(issues_ids).to eq(gids)
end
end
end
@@ -149,6 +149,8 @@ RSpec.describe 'getting an issue list for a project' do
create(:issue, :confidential, project: project)
end
+ let(:confidential_issue_gid) { confidential_issue.to_global_id.to_s }
+
context 'when the user cannot see confidential issues' do
it 'returns issues without confidential issues' do
post_graphql(query, current_user: current_user)
@@ -159,12 +161,34 @@ RSpec.describe 'getting an issue list for a project' do
expect(issue.dig('node', 'confidential')).to eq(false)
end
end
+
+ context 'filtering for confidential issues' do
+ let(:issue_filter_params) { { confidential: true } }
+
+ it 'returns no issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_data.size).to eq(0)
+ end
+ end
+
+ context 'filtering for non-confidential issues' do
+ let(:issue_filter_params) { { confidential: false } }
+
+ it 'returns correctly filtered issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(issue_a_gid, issue_b_gid)
+ end
+ end
end
context 'when the user can see confidential issues' do
- it 'returns issues with confidential issues' do
+ before do
project.add_developer(current_user)
+ end
+ it 'returns issues with confidential issues' do
post_graphql(query, current_user: current_user)
expect(issues_data.size).to eq(3)
@@ -175,6 +199,26 @@ RSpec.describe 'getting an issue list for a project' do
expect(confidentials).to eq([true, false, false])
end
+
+ context 'filtering for confidential issues' do
+ let(:issue_filter_params) { { confidential: true } }
+
+ it 'returns correctly filtered issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(confidential_issue_gid)
+ end
+ end
+
+ context 'filtering for non-confidential issues' do
+ let(:issue_filter_params) { { confidential: false } }
+
+ it 'returns correctly filtered issues' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issues_ids).to contain_exactly(issue_a_gid, issue_b_gid)
+ end
+ end
end
end
@@ -205,7 +249,7 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :DUE_DATE_ASC }
let(:first_param) { 2 }
- let(:expected_results) { [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] }
+ let(:all_records) { [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] }
end
end
@@ -213,7 +257,7 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :DUE_DATE_DESC }
let(:first_param) { 2 }
- let(:expected_results) { [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] }
+ let(:all_records) { [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] }
end
end
end
@@ -230,10 +274,10 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :RELATIVE_POSITION_ASC }
let(:first_param) { 2 }
- let(:expected_results) do
+ let(:all_records) do
[
relative_issue5.iid, relative_issue3.iid, relative_issue1.iid,
- relative_issue4.iid, relative_issue2.iid
+ relative_issue2.iid, relative_issue4.iid
]
end
end
@@ -256,7 +300,7 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :PRIORITY_ASC }
let(:first_param) { 2 }
- let(:expected_results) do
+ let(:all_records) do
[
priority_issue3.iid, priority_issue1.iid,
priority_issue2.iid, priority_issue4.iid
@@ -269,7 +313,7 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :PRIORITY_DESC }
let(:first_param) { 2 }
- let(:expected_results) do
+ let(:all_records) do
[priority_issue1.iid, priority_issue3.iid, priority_issue2.iid, priority_issue4.iid]
end
end
@@ -288,17 +332,17 @@ RSpec.describe 'getting an issue list for a project' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :LABEL_PRIORITY_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { [label_issue3.iid, label_issue1.iid, label_issue2.iid, label_issue4.iid] }
+ let(:sort_param) { :LABEL_PRIORITY_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { [label_issue3.iid, label_issue1.iid, label_issue2.iid, label_issue4.iid] }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :LABEL_PRIORITY_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { [label_issue2.iid, label_issue3.iid, label_issue1.iid, label_issue4.iid] }
+ let(:sort_param) { :LABEL_PRIORITY_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { [label_issue2.iid, label_issue3.iid, label_issue1.iid, label_issue4.iid] }
end
end
end
@@ -313,17 +357,17 @@ RSpec.describe 'getting an issue list for a project' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :MILESTONE_DUE_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { [milestone_issue2.iid, milestone_issue3.iid, milestone_issue1.iid] }
+ let(:sort_param) { :MILESTONE_DUE_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { [milestone_issue2.iid, milestone_issue3.iid, milestone_issue1.iid] }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :MILESTONE_DUE_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { [milestone_issue3.iid, milestone_issue2.iid, milestone_issue1.iid] }
+ let(:sort_param) { :MILESTONE_DUE_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { [milestone_issue3.iid, milestone_issue2.iid, milestone_issue1.iid] }
end
end
end
@@ -366,6 +410,35 @@ RSpec.describe 'getting an issue list for a project' do
end
end
+ context 'when fetching customer_relations_contacts' do
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ id
+ customerRelationsContacts {
+ nodes {
+ firstName
+ }
+ }
+ }
+ QUERY
+ end
+
+ def clean_state_query
+ run_with_clean_state(query, context: { current_user: current_user })
+ end
+
+ it 'avoids N+1 queries' do
+ create(:contact, group_id: group.id, issues: [issue_a])
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { clean_state_query }
+
+ create(:contact, group_id: group.id, issues: [issue_a])
+
+ expect { clean_state_query }.not_to exceed_all_query_limit(control)
+ end
+ end
+
context 'when fetching labels' do
let(:fields) do
<<~QUERY
@@ -526,4 +599,8 @@ RSpec.describe 'getting an issue list for a project' do
include_examples 'N+1 query check'
end
end
+
+ def issues_ids
+ graphql_dig_at(issues_data, :node, :id)
+ end
end
diff --git a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb
index 70c5bda35e1..820a5d818c7 100644
--- a/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request/pipelines_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe 'Query.project.mergeRequests.pipelines' do
before do
merge_requests.each do |mr|
- shas = mr.all_commits.limit(2).pluck(:sha)
+ shas = mr.recent_diff_head_shas
shas.each do |sha|
create(:ci_pipeline, :success, project: project, ref: mr.source_branch, sha: sha)
@@ -52,7 +52,7 @@ RSpec.describe 'Query.project.mergeRequests.pipelines' do
p_nodes = graphql_data_at(:project, :merge_requests, :nodes)
- expect(p_nodes).to all(match('iid' => be_present, 'pipelines' => match('count' => 2)))
+ expect(p_nodes).to all(match('iid' => be_present, 'pipelines' => match('count' => 1)))
end
it 'is scalable', :request_store, :use_clean_rails_memory_store_caching do
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 1b0405be09c..b0bedd99fce 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -385,7 +385,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
context 'when sorting by merged_at DESC' do
let(:sort_param) { :MERGED_AT_DESC }
- let(:expected_results) do
+ let(:all_records) do
[
merge_request_b,
merge_request_d,
@@ -418,14 +418,14 @@ RSpec.describe 'getting merge request listings nested in a project' do
query = pagination_query(params)
post_graphql(query, current_user: current_user)
- expect(results.map { |item| item["id"] }).to eq(expected_results.last(2))
+ expect(results.map { |item| item["id"] }).to eq(all_records.last(2))
end
end
end
context 'when sorting by closed_at DESC' do
let(:sort_param) { :CLOSED_AT_DESC }
- let(:expected_results) do
+ let(:all_records) do
[
merge_request_b,
merge_request_d,
@@ -458,7 +458,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
query = pagination_query(params)
post_graphql(query, current_user: current_user)
- expect(results.map { |item| item["id"] }).to eq(expected_results.last(2))
+ expect(results.map { |item| item["id"] }).to eq(all_records.last(2))
end
end
end
diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb
index 8ccdb955ed9..2816ce90a6b 100644
--- a/spec/requests/api/graphql/project/releases_spec.rb
+++ b/spec/requests/api/graphql/project/releases_spec.rb
@@ -322,17 +322,17 @@ RSpec.describe 'Query.project(fullPath).releases()' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :RELEASED_AT_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
+ let(:sort_param) { :RELEASED_AT_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :RELEASED_AT_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
+ let(:sort_param) { :RELEASED_AT_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
end
end
end
@@ -346,17 +346,17 @@ RSpec.describe 'Query.project(fullPath).releases()' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
+ let(:sort_param) { :CREATED_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
+ let(:sort_param) { :CREATED_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
end
end
end
diff --git a/spec/requests/api/graphql/users_spec.rb b/spec/requests/api/graphql/users_spec.rb
index 22b68fbc9bb..67cd35ee545 100644
--- a/spec/requests/api/graphql/users_spec.rb
+++ b/spec/requests/api/graphql/users_spec.rb
@@ -114,17 +114,17 @@ RSpec.describe 'Users' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_ASC }
- let(:first_param) { 1 }
- let(:expected_results) { ascending_users }
+ let(:sort_param) { :CREATED_ASC }
+ let(:first_param) { 1 }
+ let(:all_records) { ascending_users }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_DESC }
- let(:first_param) { 1 }
- let(:expected_results) { ascending_users.reverse }
+ let(:sort_param) { :CREATED_DESC }
+ let(:first_param) { 1 }
+ let(:all_records) { ascending_users.reverse }
end
end
end
diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb
index fdbf910e4bc..bf29bd91414 100644
--- a/spec/requests/api/group_container_repositories_spec.rb
+++ b/spec/requests/api/group_container_repositories_spec.rb
@@ -20,12 +20,14 @@ RSpec.describe API::GroupContainerRepositories do
end
let(:api_user) { reporter }
+ let(:params) { {} }
before do
group.add_reporter(reporter)
group.add_guest(guest)
stub_container_registry_config(enabled: true)
+ stub_container_registry_info
root_repository
test_repository
@@ -35,10 +37,13 @@ RSpec.describe API::GroupContainerRepositories do
let(:url) { "/groups/#{group.id}/registry/repositories" }
let(:snowplow_gitlab_standard_context) { { user: api_user, namespace: group } }
- subject { get api(url, api_user) }
+ subject { get api(url, api_user), params: params }
it_behaves_like 'rejected container repository access', :guest, :forbidden
it_behaves_like 'rejected container repository access', :anonymous, :not_found
+ it_behaves_like 'handling network errors with the container registry' do
+ let(:params) { { tags: true } }
+ end
it_behaves_like 'returns repositories for allowed users', :reporter, 'group' do
let(:object) { group }
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 38abedde7da..2c7e2ecff85 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -728,16 +728,16 @@ RSpec.describe API::Groups do
end
it 'avoids N+1 queries with project links' do
- get api("/groups/#{group1.id}", admin)
+ get api("/groups/#{group1.id}", user1)
control_count = ActiveRecord::QueryRecorder.new do
- get api("/groups/#{group1.id}", admin)
+ get api("/groups/#{group1.id}", user1)
end.count
create(:project, namespace: group1)
expect do
- get api("/groups/#{group1.id}", admin)
+ get api("/groups/#{group1.id}", user1)
end.not_to exceed_query_limit(control_count)
end
@@ -746,7 +746,7 @@ RSpec.describe API::Groups do
create(:group_group_link, shared_group: group1, shared_with_group: create(:group))
control_count = ActiveRecord::QueryRecorder.new do
- get api("/groups/#{group1.id}", admin)
+ get api("/groups/#{group1.id}", user1)
end.count
# setup "n" more shared groups
@@ -755,7 +755,7 @@ RSpec.describe API::Groups do
# test that no of queries for 1 shared group is same as for n shared groups
expect do
- get api("/groups/#{group1.id}", admin)
+ get api("/groups/#{group1.id}", user1)
end.not_to exceed_query_limit(control_count)
end
end
@@ -1179,6 +1179,20 @@ RSpec.describe API::Groups do
expect(json_response.length).to eq(1)
expect(json_response.first['name']).to eq(project1.name)
end
+
+ it 'avoids N+1 queries' do
+ get api("/groups/#{group1.id}/projects", user1)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/groups/#{group1.id}/projects", user1)
+ end.count
+
+ create(:project, namespace: group1)
+
+ expect do
+ get api("/groups/#{group1.id}/projects", user1)
+ end.not_to exceed_query_limit(control_count)
+ end
end
context "when authenticated as admin" do
@@ -1196,20 +1210,6 @@ RSpec.describe API::Groups do
expect(response).to have_gitlab_http_status(:not_found)
end
-
- it 'avoids N+1 queries' do
- get api("/groups/#{group1.id}/projects", admin)
-
- control_count = ActiveRecord::QueryRecorder.new do
- get api("/groups/#{group1.id}/projects", admin)
- end.count
-
- create(:project, namespace: group1)
-
- expect do
- get api("/groups/#{group1.id}/projects", admin)
- end.not_to exceed_query_limit(control_count)
- end
end
context 'when using group path in URL' do
diff --git a/spec/requests/api/helm_packages_spec.rb b/spec/requests/api/helm_packages_spec.rb
index 3236857c5fc..5212e225351 100644
--- a/spec/requests/api/helm_packages_spec.rb
+++ b/spec/requests/api/helm_packages_spec.rb
@@ -18,11 +18,11 @@ RSpec.describe API::HelmPackages do
let_it_be(:other_package) { create(:npm_package, project: project) }
describe 'GET /api/v4/projects/:id/packages/helm/:channel/index.yaml' do
- let(:url) { "/projects/#{project_id}/packages/helm/stable/index.yaml" }
+ let(:project_id) { project.id }
+ let(:channel) { 'stable' }
+ let(:url) { "/projects/#{project_id}/packages/helm/#{channel}/index.yaml" }
context 'with a project id' do
- let(:project_id) { project.id }
-
it_behaves_like 'handling helm chart index requests'
end
@@ -31,6 +31,18 @@ RSpec.describe API::HelmPackages do
it_behaves_like 'handling helm chart index requests'
end
+
+ context 'with dot in channel' do
+ let(:channel) { 'with.dot' }
+
+ subject { get api(url) }
+
+ before do
+ project.update!(visibility: 'public')
+ end
+
+ it_behaves_like 'returning response status', :success
+ end
end
describe 'GET /api/v4/projects/:id/packages/helm/:channel/charts/:file_name.tgz' do
diff --git a/spec/requests/api/integrations_spec.rb b/spec/requests/api/integrations_spec.rb
new file mode 100644
index 00000000000..649647804c0
--- /dev/null
+++ b/spec/requests/api/integrations_spec.rb
@@ -0,0 +1,363 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe API::Integrations do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:user2) { create(:user) }
+
+ let_it_be(:project, reload: true) do
+ create(:project, creator_id: user.id, namespace: user.namespace)
+ end
+
+ %w[integrations services].each do |endpoint|
+ describe "GET /projects/:id/#{endpoint}" do
+ it 'returns authentication error when unauthenticated' do
+ get api("/projects/#{project.id}/#{endpoint}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it "returns error when authenticated but user is not a project owner" do
+ project.add_developer(user2)
+ get api("/projects/#{project.id}/#{endpoint}", user2)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ context 'with integrations' do
+ let!(:active_integration) { create(:emails_on_push_integration, project: project, active: true) }
+ let!(:integration) { create(:custom_issue_tracker_integration, project: project, active: false) }
+
+ it "returns a list of all active integrations" do
+ get api("/projects/#{project.id}/#{endpoint}", user)
+
+ aggregate_failures 'expect successful response with all active integrations' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['slug']).to eq('emails-on-push')
+ expect(response).to match_response_schema('public_api/v4/integrations')
+ end
+ end
+ end
+ end
+
+ Integration.available_integration_names.each do |integration|
+ describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
+ include_context integration
+
+ it "updates #{integration} settings" do
+ put api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user), params: integration_attrs
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ current_integration = project.integrations.first
+ events = current_integration.event_names.empty? ? ["foo"].freeze : current_integration.event_names
+ query_strings = []
+ events.each do |event|
+ query_strings << "#{event}=#{!current_integration[event]}"
+ end
+ query_strings = query_strings.join('&')
+
+ put api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}?#{query_strings}", user), params: integration_attrs
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['slug']).to eq(dashed_integration)
+ events.each do |event|
+ next if event == "foo"
+
+ expect(project.integrations.first[event]).not_to eq(current_integration[event]),
+ "expected #{!current_integration[event]} for event #{event} for #{endpoint} #{current_integration.title}, got #{current_integration[event]}"
+ end
+ end
+
+ it "returns if required fields missing" do
+ required_attributes = integration_attrs_list.select do |attr|
+ integration_klass.validators_on(attr).any? do |v|
+ v.instance_of?(ActiveRecord::Validations::PresenceValidator) &&
+ # exclude presence validators with conditional since those are not really required
+ ![:if, :unless].any? { |cond| v.options.include?(cond) }
+ end
+ end
+
+ if required_attributes.empty?
+ expected_code = :ok
+ else
+ integration_attrs.delete(required_attributes.sample)
+ expected_code = :bad_request
+ end
+
+ put api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user), params: integration_attrs
+
+ expect(response).to have_gitlab_http_status(expected_code)
+ end
+ end
+
+ describe "DELETE /projects/:id/#{endpoint}/#{integration.dasherize}" do
+ include_context integration
+
+ before do
+ initialize_integration(integration)
+ end
+
+ it "deletes #{integration}" do
+ delete api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ project.send(integration_method).reload
+ expect(project.send(integration_method).activated?).to be_falsey
+ end
+ end
+
+ describe "GET /projects/:id/#{endpoint}/#{integration.dasherize}" do
+ include_context integration
+
+ let!(:initialized_integration) { initialize_integration(integration, active: true) }
+
+ let_it_be(:project2) do
+ create(:project, creator_id: user.id, namespace: user.namespace)
+ end
+
+ def deactive_integration!
+ return initialized_integration.update!(active: false) unless initialized_integration.is_a?(::Integrations::Prometheus)
+
+ # Integrations::Prometheus sets `#active` itself within a `before_save`:
+ initialized_integration.manual_configuration = false
+ initialized_integration.save!
+ end
+
+ it 'returns authentication error when unauthenticated' do
+ get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}")
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it "returns all properties of active integration #{integration}" do
+ get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user)
+
+ expect(initialized_integration).to be_active
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names)
+ end
+
+ it "returns all properties of inactive integration #{integration}" do
+ deactive_integration!
+
+ get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user)
+
+ expect(initialized_integration).not_to be_active
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names)
+ end
+
+ it "returns not found if integration does not exist" do
+ get api("/projects/#{project2.id}/#{endpoint}/#{dashed_integration}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Integration Not Found')
+ end
+
+ it "returns not found if integration exists but is in `Project#disabled_integrations`" do
+ expect_next_found_instance_of(Project) do |project|
+ expect(project).to receive(:disabled_integrations).at_least(:once).and_return([integration])
+ end
+
+ get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Integration Not Found')
+ end
+
+ it "returns error when authenticated but not a project owner" do
+ project.add_developer(user2)
+ get api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user2)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+
+ describe "POST /projects/:id/#{endpoint}/:slug/trigger" do
+ describe 'Mattermost integration' do
+ let(:integration_name) { 'mattermost_slash_commands' }
+
+ context 'when no integration is available' do
+ it 'returns a not found message' do
+ post api("/projects/#{project.id}/#{endpoint}/idonotexist/trigger")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response["error"]).to eq("404 Not Found")
+ end
+ end
+
+ context 'when the integration exists' do
+ let(:params) { { token: 'token' } }
+
+ context 'when the integration is not active' do
+ before do
+ project.create_mattermost_slash_commands_integration(
+ active: false,
+ properties: params
+ )
+ end
+
+ it 'when the integration is inactive' do
+ post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the integration is active' do
+ before do
+ project.create_mattermost_slash_commands_integration(
+ active: true,
+ properties: params
+ )
+ end
+
+ it 'returns status 200' do
+ post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when the project can not be found' do
+ it 'returns a generic 404' do
+ post api("/projects/404/#{endpoint}/#{integration_name}/trigger"), params: params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response["message"]).to eq("404 Integration Not Found")
+ end
+ end
+ end
+ end
+
+ describe 'Slack Integration' do
+ let(:integration_name) { 'slack_slash_commands' }
+
+ before do
+ project.create_slack_slash_commands_integration(
+ active: true,
+ properties: { token: 'token' }
+ )
+ end
+
+ it 'returns status 200' do
+ post api("/projects/#{project.id}/#{endpoint}/#{integration_name}/trigger"), params: { token: 'token', text: 'help' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['response_type']).to eq("ephemeral")
+ end
+ end
+ end
+
+ describe 'Mattermost integration' do
+ let(:integration_name) { 'mattermost' }
+ let(:params) do
+ { webhook: 'https://hook.example.com', username: 'username' }
+ end
+
+ before do
+ project.create_mattermost_integration(
+ active: true,
+ properties: params
+ )
+ end
+
+ it 'accepts a username for update' do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(username: 'new_username')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties']['username']).to eq('new_username')
+ end
+ end
+
+ describe 'Microsoft Teams integration' do
+ let(:integration_name) { 'microsoft-teams' }
+ let(:params) do
+ {
+ webhook: 'https://hook.example.com',
+ branches_to_be_notified: 'default',
+ notify_only_broken_pipelines: false
+ }
+ end
+
+ before do
+ project.create_microsoft_teams_integration(
+ active: true,
+ properties: params
+ )
+ end
+
+ it 'accepts branches_to_be_notified for update' do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
+ params: params.merge(branches_to_be_notified: 'all')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties']['branches_to_be_notified']).to eq('all')
+ end
+
+ it 'accepts notify_only_broken_pipelines for update' do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user),
+ params: params.merge(notify_only_broken_pipelines: true)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
+ end
+ end
+
+ describe 'Hangouts Chat integration' do
+ let(:integration_name) { 'hangouts-chat' }
+ let(:params) do
+ {
+ webhook: 'https://hook.example.com',
+ branches_to_be_notified: 'default'
+ }
+ end
+
+ before do
+ project.create_hangouts_chat_integration(
+ active: true,
+ properties: params
+ )
+ end
+
+ it 'accepts branches_to_be_notified for update', :aggregate_failures do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: params.merge(branches_to_be_notified: 'all')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['properties']['branches_to_be_notified']).to eq('all')
+ end
+
+ it 'only requires the webhook param' do
+ put api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user), params: { webhook: 'https://hook.example.com' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ describe 'Pipelines Email Integration' do
+ let(:integration_name) { 'pipelines-email' }
+
+ context 'notify_only_broken_pipelines property was saved as a string' do
+ before do
+ project.create_pipelines_email_integration(
+ active: false,
+ properties: {
+ "notify_only_broken_pipelines": "true",
+ "branches_to_be_notified": "default"
+ }
+ )
+ end
+
+ it 'returns boolean values for notify_only_broken_pipelines' do
+ get api("/projects/#{project.id}/#{endpoint}/#{integration_name}", user)
+
+ expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index 49756df61c6..aeca4e435f4 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -609,7 +609,7 @@ RSpec.describe API::Internal::Base do
end
context 'with Project' do
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let(:expected_params) { { user: key.user.username, project: project.full_path, caller_id: "POST /api/:version/internal/allowed" } }
subject { push(key, project) }
@@ -617,7 +617,7 @@ RSpec.describe API::Internal::Base do
end
context 'with PersonalSnippet' do
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let(:expected_params) { { user: key.user.username, caller_id: "POST /api/:version/internal/allowed" } }
subject { push(key, personal_snippet) }
@@ -625,7 +625,7 @@ RSpec.describe API::Internal::Base do
end
context 'with ProjectSnippet' do
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let(:expected_params) { { user: key.user.username, project: project_snippet.project.full_path, caller_id: "POST /api/:version/internal/allowed" } }
subject { push(key, project_snippet) }
@@ -1197,7 +1197,7 @@ RSpec.describe API::Internal::Base do
subject
end
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let(:expected_params) { expected_context }
end
end
diff --git a/spec/requests/api/internal/kubernetes_spec.rb b/spec/requests/api/internal/kubernetes_spec.rb
index 24422f7b0dd..245e4e6ba15 100644
--- a/spec/requests/api/internal/kubernetes_spec.rb
+++ b/spec/requests/api/internal/kubernetes_spec.rb
@@ -177,94 +177,4 @@ RSpec.describe API::Internal::Kubernetes do
end
end
end
-
- describe 'GET /internal/kubernetes/project_info' do
- def send_request(headers: {}, params: {})
- get api('/internal/kubernetes/project_info'), params: params, headers: headers.reverse_merge(jwt_auth_headers)
- end
-
- include_examples 'authorization'
- include_examples 'agent authentication'
-
- context 'an agent is found' do
- let_it_be(:agent_token) { create(:cluster_agent_token) }
-
- shared_examples 'agent token tracking'
-
- context 'project is public' do
- let(:project) { create(:project, :public) }
-
- it 'returns expected data', :aggregate_failures do
- send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
-
- expect(response).to have_gitlab_http_status(:success)
-
- expect(json_response).to match(
- a_hash_including(
- 'project_id' => project.id,
- 'gitaly_info' => a_hash_including(
- 'address' => match(/\.socket$/),
- 'token' => 'secret',
- 'features' => {}
- ),
- 'gitaly_repository' => a_hash_including(
- 'storage_name' => project.repository_storage,
- 'relative_path' => project.disk_path + '.git',
- 'gl_repository' => "project-#{project.id}",
- 'gl_project_path' => project.full_path
- )
- )
- )
- end
-
- context 'repository is for project members only' do
- let(:project) { create(:project, :public, :repository_private) }
-
- it 'returns 404' do
- send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- context 'project is private' do
- let(:project) { create(:project, :private) }
-
- it 'returns 404' do
- send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
-
- context 'and agent belongs to project' do
- let(:agent_token) { create(:cluster_agent_token, agent: create(:cluster_agent, project: project)) }
-
- it 'returns 200' do
- send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
-
- expect(response).to have_gitlab_http_status(:success)
- end
- end
- end
-
- context 'project is internal' do
- let(:project) { create(:project, :internal) }
-
- it 'returns 404' do
- send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'project does not exist' do
- it 'returns 404' do
- send_request(params: { id: non_existing_record_id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
- end
end
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index 8a33e63b80b..9204ee4d7f0 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -138,6 +138,12 @@ RSpec.describe API::Issues do
expect(json_response).to be_an Array
end
+ it_behaves_like 'issuable anonymous search' do
+ let(:url) { '/issues' }
+ let(:issuable) { issue }
+ let(:result) { issuable.id }
+ end
+
it 'returns authentication error without any scope' do
get api('/issues')
@@ -256,6 +262,38 @@ RSpec.describe API::Issues do
it_behaves_like 'issues statistics'
end
+
+ context 'with search param' do
+ let(:params) { { scope: 'all', search: 'foo' } }
+ let(:counts) { { all: 1, closed: 0, opened: 1 } }
+
+ it_behaves_like 'issues statistics'
+
+ context 'with anonymous user' do
+ let(:user) { nil }
+
+ context 'with disable_anonymous_search disabled' do
+ before do
+ stub_feature_flags(disable_anonymous_search: false)
+ end
+
+ it_behaves_like 'issues statistics'
+ end
+
+ context 'with disable_anonymous_search enabled' do
+ before do
+ stub_feature_flags(disable_anonymous_search: true)
+ end
+
+ it 'returns a unprocessable entity 422' do
+ get api("/issues_statistics"), params: params
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to include('User must be authenticated to use search')
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/issues/post_projects_issues_spec.rb b/spec/requests/api/issues/post_projects_issues_spec.rb
index 9d3bd26a200..82692366589 100644
--- a/spec/requests/api/issues/post_projects_issues_spec.rb
+++ b/spec/requests/api/issues/post_projects_issues_spec.rb
@@ -8,15 +8,15 @@ RSpec.describe API::Issues do
create(:project, :public, creator_id: user.id, namespace: user.namespace)
end
- let(:user2) { create(:user) }
- let(:non_member) { create(:user) }
- let_it_be(:guest) { create(:user) }
- let_it_be(:author) { create(:author) }
- let_it_be(:assignee) { create(:assignee) }
- let(:admin) { create(:user, :admin) }
- let(:issue_title) { 'foo' }
- let(:issue_description) { 'closed' }
- let!(:closed_issue) do
+ let_it_be(:user2) { create(:user) }
+ let_it_be(:non_member) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:author) { create(:author) }
+ let_it_be(:milestone) { create(:milestone, title: '1.0.0', project: project) }
+ let_it_be(:assignee) { create(:assignee) }
+ let_it_be(:admin) { create(:user, :admin) }
+
+ let_it_be(:closed_issue) do
create :closed_issue,
author: user,
assignees: [user],
@@ -28,7 +28,7 @@ RSpec.describe API::Issues do
closed_at: 1.hour.ago
end
- let!(:confidential_issue) do
+ let_it_be(:confidential_issue) do
create :issue,
:confidential,
project: project,
@@ -38,7 +38,7 @@ RSpec.describe API::Issues do
updated_at: 2.hours.ago
end
- let!(:issue) do
+ let_it_be(:issue) do
create :issue,
author: user,
assignees: [user],
@@ -46,22 +46,21 @@ RSpec.describe API::Issues do
milestone: milestone,
created_at: generate(:past_time),
updated_at: 1.hour.ago,
- title: issue_title,
- description: issue_description
+ title: 'foo',
+ description: 'closed'
end
+ let_it_be(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
+
let_it_be(:label) do
create(:label, title: 'label', color: '#FFAABB', project: project)
end
let!(:label_link) { create(:label_link, label: label, target: issue) }
- let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
let_it_be(:empty_milestone) do
create(:milestone, title: '2.0.0', project: project)
end
- let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
-
let(:no_milestone_title) { 'None' }
let(:any_milestone_title) { 'Any' }
@@ -400,16 +399,15 @@ RSpec.describe API::Issues do
end
context 'when request exceeds the rate limit' do
- before do
+ it 'prevents users from creating more issues' do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true)
- end
- it 'prevents users from creating more issues' do
post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] }
- expect(response).to have_gitlab_http_status(:too_many_requests)
expect(json_response['message']['error']).to eq('This endpoint has been requested too many times. Try again later.')
+
+ expect(response).to have_gitlab_http_status(:too_many_requests)
end
end
end
@@ -517,7 +515,7 @@ RSpec.describe API::Issues do
end
context 'when using the issue ID instead of iid' do
- it 'returns 404 when trying to move an issue' do
+ it 'returns 404 when trying to move an issue', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do
post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
params: { to_project_id: target_project.id }
@@ -556,6 +554,114 @@ RSpec.describe API::Issues do
end
end
+ describe '/projects/:id/issues/:issue_iid/clone' do
+ let_it_be(:valid_target_project) { create(:project) }
+ let_it_be(:invalid_target_project) { create(:project) }
+
+ before_all do
+ valid_target_project.add_maintainer(user)
+ end
+
+ context 'when user can admin the issue' do
+ context 'when the user can admin the target project' do
+ it 'clones the issue' do
+ expect do
+ post_clone_issue(user, issue, valid_target_project)
+ end.to change { valid_target_project.issues.count }.by(1)
+
+ cloned_issue = Issue.last
+
+ expect(cloned_issue.notes.count).to eq(2)
+ expect(cloned_issue.notes.pluck(:note)).not_to include(issue.notes.first.note)
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(cloned_issue.id)
+ expect(json_response['project_id']).to eq(valid_target_project.id)
+ end
+
+ context 'when target project is the same source project' do
+ it 'clones the issue' do
+ expect do
+ post_clone_issue(user, issue, issue.project)
+ end.to change { issue.reset.project.issues.count }.by(1)
+
+ cloned_issue = Issue.last
+
+ expect(cloned_issue.notes.count).to eq(2)
+ expect(cloned_issue.notes.pluck(:note)).not_to include(issue.notes.first.note)
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(cloned_issue.id)
+ expect(json_response['project_id']).to eq(issue.project.id)
+ end
+ end
+ end
+ end
+
+ context 'when the user does not have the permission to clone issues' do
+ it 'returns 400' do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user),
+ params: { to_project_id: invalid_target_project.id }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq(s_('CloneIssue|Cannot clone issue due to insufficient permissions!'))
+ end
+ end
+
+ context 'when using the issue ID instead of iid' do
+ it 'returns 404', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/clone", user),
+ params: { to_project_id: valid_target_project.id }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Issue Not Found')
+ end
+ end
+
+ context 'when issue does not exist' do
+ it 'returns 404' do
+ post api("/projects/#{project.id}/issues/12300/clone", user),
+ params: { to_project_id: valid_target_project.id }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Issue Not Found')
+ end
+ end
+
+ context 'when source project does not exist' do
+ it 'returns 404' do
+ post api("/projects/0/issues/#{issue.iid}/clone", user),
+ params: { to_project_id: valid_target_project.id }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+ end
+
+ context 'when target project does not exist' do
+ it 'returns 404' do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user),
+ params: { to_project_id: 0 }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+ end
+
+ it 'clones the issue with notes when with_notes is true' do
+ expect do
+ post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user),
+ params: { to_project_id: valid_target_project.id, with_notes: true }
+ end.to change { valid_target_project.issues.count }.by(1)
+
+ cloned_issue = Issue.last
+
+ expect(cloned_issue.notes.count).to eq(3)
+ expect(cloned_issue.notes.pluck(:note)).to include(issue.notes.first.note)
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['id']).to eq(cloned_issue.id)
+ expect(json_response['project_id']).to eq(valid_target_project.id)
+ end
+ end
+
describe 'POST :id/issues/:issue_iid/subscribe' do
it 'subscribes to an issue' do
post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2)
@@ -576,7 +682,7 @@ RSpec.describe API::Issues do
expect(response).to have_gitlab_http_status(:not_found)
end
- it 'returns 404 if the issue ID is used instead of the iid' do
+ it 'returns 404 if the issue ID is used instead of the iid', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do
post api("/projects/#{project.id}/issues/#{issue.id}/subscribe", user)
expect(response).to have_gitlab_http_status(:not_found)
@@ -609,7 +715,7 @@ RSpec.describe API::Issues do
expect(response).to have_gitlab_http_status(:not_found)
end
- it 'returns 404 if using the issue ID instead of iid' do
+ it 'returns 404 if using the issue ID instead of iid', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do
post api("/projects/#{project.id}/issues/#{issue.id}/unsubscribe", user)
expect(response).to have_gitlab_http_status(:not_found)
@@ -621,4 +727,9 @@ RSpec.describe API::Issues do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ def post_clone_issue(current_user, issue, target_project)
+ post api("/projects/#{issue.project.id}/issues/#{issue.iid}/clone", current_user),
+ params: { to_project_id: target_project.id }
+ end
end
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
index 07111dd1d62..5a682ee8532 100644
--- a/spec/requests/api/maven_packages_spec.rb
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -798,8 +798,6 @@ RSpec.describe API::MavenPackages do
end
describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name' do
- include_context 'workhorse headers'
-
let(:send_rewritten_field) { true }
let(:file_upload) { fixture_file_upload('spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar') }
@@ -833,6 +831,8 @@ RSpec.describe API::MavenPackages do
context 'when params from workhorse are correct' do
let(:params) { { file: file_upload } }
+ subject { upload_file_with_token(params: params) }
+
context 'file size is too large' do
it 'rejects the request' do
allow_next_instance_of(UploadedFile) do |uploaded_file|
@@ -851,18 +851,20 @@ RSpec.describe API::MavenPackages do
expect(response).to have_gitlab_http_status(:bad_request)
end
- context 'without workhorse header' do
- let(:workhorse_headers) { {} }
-
- subject { upload_file_with_token(params: params) }
-
- it_behaves_like 'package workhorse uploads'
- end
+ it_behaves_like 'package workhorse uploads'
context 'event tracking' do
- subject { upload_file_with_token(params: params) }
-
it_behaves_like 'a package tracking event', described_class.name, 'push_package'
+
+ context 'when the package file fails to be created' do
+ before do
+ allow_next_instance_of(::Packages::CreatePackageFileService) do |create_package_file_service|
+ allow(create_package_file_service).to receive(:execute).and_raise(StandardError)
+ end
+ end
+
+ it_behaves_like 'not a package tracking event'
+ end
end
it 'creates package and stores package file' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 7a587e82683..bdbc73a59d8 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -49,6 +49,12 @@ RSpec.describe API::MergeRequests do
expect_successful_response_with_paginated_array
end
+
+ it_behaves_like 'issuable anonymous search' do
+ let(:url) { endpoint_path }
+ let(:issuable) { merge_request }
+ let(:result) { [merge_request_merged.id, merge_request_locked.id, merge_request_closed.id, merge_request.id] }
+ end
end
context 'when authenticated' do
@@ -613,6 +619,12 @@ RSpec.describe API::MergeRequests do
)
end
+ it_behaves_like 'issuable anonymous search' do
+ let(:url) { '/merge_requests' }
+ let(:issuable) { merge_request }
+ let(:result) { [merge_request_merged.id, merge_request_locked.id, merge_request_closed.id, merge_request.id] }
+ end
+
it "returns authentication error without any scope" do
get api("/merge_requests")
diff --git a/spec/requests/api/package_files_spec.rb b/spec/requests/api/package_files_spec.rb
index 137ded050c5..eb1f04d193e 100644
--- a/spec/requests/api/package_files_spec.rb
+++ b/spec/requests/api/package_files_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe API::PackageFiles do
expect(response).to have_gitlab_http_status(:not_found)
end
- it 'returns 404 for a user without access to the project' do
+ it 'returns 404 for a user without access to the project', :sidekiq_inline do
project.team.truncate
get api(url, user)
diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb
index 1170a9ba6cb..196b0395ec0 100644
--- a/spec/requests/api/project_container_repositories_spec.rb
+++ b/spec/requests/api/project_container_repositories_spec.rb
@@ -52,6 +52,7 @@ RSpec.describe API::ProjectContainerRepositories do
test_repository
stub_container_registry_config(enabled: true)
+ stub_container_registry_info
end
shared_context 'using API user' do
@@ -105,6 +106,9 @@ RSpec.describe API::ProjectContainerRepositories do
it_behaves_like 'rejected container repository access', :guest, :forbidden unless context == 'using job token'
it_behaves_like 'rejected container repository access', :anonymous, :not_found
it_behaves_like 'a package tracking event', described_class.name, 'list_repositories'
+ it_behaves_like 'handling network errors with the container registry' do
+ let(:params) { { tags: true } }
+ end
it_behaves_like 'returns repositories for allowed users', :reporter, 'project' do
let(:object) { project }
@@ -154,6 +158,7 @@ RSpec.describe API::ProjectContainerRepositories do
it_behaves_like 'rejected container repository access', :guest, :forbidden unless context == 'using job token'
it_behaves_like 'rejected container repository access', :anonymous, :not_found
+ it_behaves_like 'handling network errors with the container registry'
context 'for reporter' do
let(:api_user) { reporter }
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 06f4475ef79..b9c458373a8 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -457,4 +457,143 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do
end
end
end
+
+ describe 'export relations' do
+ let(:relation) { 'labels' }
+ let(:download_path) { "/projects/#{project.id}/export_relations/download?relation=#{relation}" }
+ let(:path) { "/projects/#{project.id}/export_relations" }
+
+ let_it_be(:status_path) { "/projects/#{project.id}/export_relations/status" }
+
+ context 'when user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe 'POST /projects/:id/export_relations' do
+ it 'accepts the request' do
+ post api(path, user)
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+
+ context 'when response is not success' do
+ it 'returns api error' do
+ allow_next_instance_of(BulkImports::ExportService) do |service|
+ allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'error', http_status: :error))
+ end
+
+ post api(path, user)
+
+ expect(response).to have_gitlab_http_status(:error)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/export_relations/download' do
+ let_it_be(:export) { create(:bulk_import_export, project: project, relation: 'labels') }
+ let_it_be(:upload) { create(:bulk_import_export_upload, export: export) }
+
+ context 'when export file exists' do
+ it 'downloads exported project relation archive' do
+ upload.update!(export_file: fixture_file_upload('spec/fixtures/bulk_imports/gz/labels.ndjson.gz'))
+
+ get api(download_path, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.header['Content-Disposition']).to eq("attachment; filename=\"labels.ndjson.gz\"; filename*=UTF-8''labels.ndjson.gz")
+ end
+ end
+
+ context 'when relation is not portable' do
+ let(:relation) { ::BulkImports::FileTransfer::ProjectConfig.new(project).skipped_relations.first }
+
+ it_behaves_like '400 response' do
+ let(:request) { get api(download_path, user) }
+ end
+ end
+
+ context 'when export file does not exist' do
+ it 'returns 404' do
+ allow(upload).to receive(:export_file).and_return(nil)
+
+ get api(download_path, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/export_relations/status' do
+ it 'returns a list of relation export statuses' do
+ create(:bulk_import_export, :started, project: project, relation: 'labels')
+ create(:bulk_import_export, :finished, project: project, relation: 'milestones')
+ create(:bulk_import_export, :failed, project: project, relation: 'project_badges')
+
+ get api(status_path, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.pluck('relation')).to contain_exactly('labels', 'milestones', 'project_badges')
+ expect(json_response.pluck('status')).to contain_exactly(-1, 0, 1)
+ end
+ end
+
+ context 'with bulk_import FF disabled' do
+ before do
+ stub_feature_flags(bulk_import: false)
+ end
+
+ describe 'POST /projects/:id/export_relations' do
+ it_behaves_like '404 response' do
+ let(:request) { post api(path, user) }
+ end
+ end
+
+ describe 'GET /projects/:id/export_relations/download' do
+ let_it_be(:export) { create(:bulk_import_export, project: project, relation: 'labels') }
+ let_it_be(:upload) { create(:bulk_import_export_upload, export: export) }
+
+ before do
+ upload.update!(export_file: fixture_file_upload('spec/fixtures/bulk_imports/gz/labels.ndjson.gz'))
+ end
+
+ it_behaves_like '404 response' do
+ let(:request) { post api(path, user) }
+ end
+ end
+
+ describe 'GET /projects/:id/export_relations/status' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(status_path, user) }
+ end
+ end
+ end
+ end
+
+ context 'when user is a developer' do
+ let_it_be(:developer) { create(:user) }
+
+ before do
+ project.add_developer(developer)
+ end
+
+ describe 'POST /projects/:id/export_relations' do
+ it_behaves_like '403 response' do
+ let(:request) { post api(path, developer) }
+ end
+ end
+
+ describe 'GET /projects/:id/export_relations/download' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(download_path, developer) }
+ end
+ end
+
+ describe 'GET /projects/:id/export_relations/status' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(status_path, developer) }
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index be8a6c7bdcf..b5d3dcee804 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2591,7 +2591,7 @@ RSpec.describe API::Projects do
end
end
- it_behaves_like 'storing arguments in the application context' do
+ it_behaves_like 'storing arguments in the application context for the API' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) }
let(:expected_params) { { user: user.username, project: project.full_path } }
@@ -2684,26 +2684,9 @@ RSpec.describe API::Projects do
context 'when authenticated' do
context 'valid request' do
- context 'when sort_by_project_authorizations_user_id FF is off' do
- before do
- stub_feature_flags(sort_by_project_users_by_project_authorizations_user_id: false)
- end
-
- it_behaves_like 'project users response' do
- let(:project) { project4 }
- let(:current_user) { user4 }
- end
- end
-
- context 'when sort_by_project_authorizations_user_id FF is on' do
- before do
- stub_feature_flags(sort_by_project_users_by_project_authorizations_user_id: true)
- end
-
- it_behaves_like 'project users response' do
- let(:project) { project4 }
- let(:current_user) { user4 }
- end
+ it_behaves_like 'project users response' do
+ let(:project) { project4 }
+ let(:current_user) { user4 }
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index a576e1ab1ee..f05f125c974 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -305,6 +305,18 @@ RSpec.describe API::Repositories do
end
end
+ it 'returns only a part of the repository with path set' do
+ path = 'bar'
+ get api("#{route}?path=#{path}", current_user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ type, params = workhorse_send_data
+
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\-#{path}\.tar.gz/)
+ end
+
it 'rate limits user when thresholds hit' do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true)
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
deleted file mode 100644
index e550132e776..00000000000
--- a/spec/requests/api/services_spec.rb
+++ /dev/null
@@ -1,361 +0,0 @@
-# frozen_string_literal: true
-
-require "spec_helper"
-
-RSpec.describe API::Services do
- let_it_be(:user) { create(:user) }
- let_it_be(:user2) { create(:user) }
-
- let_it_be(:project, reload: true) do
- create(:project, creator_id: user.id, namespace: user.namespace)
- end
-
- describe "GET /projects/:id/services" do
- it 'returns authentication error when unauthenticated' do
- get api("/projects/#{project.id}/services")
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
-
- it "returns error when authenticated but user is not a project owner" do
- project.add_developer(user2)
- get api("/projects/#{project.id}/services", user2)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- context 'with integrations' do
- let!(:active_integration) { create(:emails_on_push_integration, project: project, active: true) }
- let!(:integration) { create(:custom_issue_tracker_integration, project: project, active: false) }
-
- it "returns a list of all active integrations" do
- get api("/projects/#{project.id}/services", user)
-
- aggregate_failures 'expect successful response with all active integrations' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response).to be_an Array
- expect(json_response.count).to eq(1)
- expect(json_response.first['slug']).to eq('emails-on-push')
- expect(response).to match_response_schema('public_api/v4/services')
- end
- end
- end
- end
-
- Integration.available_integration_names.each do |integration|
- describe "PUT /projects/:id/services/#{integration.dasherize}" do
- include_context integration
-
- it "updates #{integration} settings" do
- put api("/projects/#{project.id}/services/#{dashed_integration}", user), params: integration_attrs
-
- expect(response).to have_gitlab_http_status(:ok)
-
- current_integration = project.integrations.first
- events = current_integration.event_names.empty? ? ["foo"].freeze : current_integration.event_names
- query_strings = []
- events.each do |event|
- query_strings << "#{event}=#{!current_integration[event]}"
- end
- query_strings = query_strings.join('&')
-
- put api("/projects/#{project.id}/services/#{dashed_integration}?#{query_strings}", user), params: integration_attrs
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['slug']).to eq(dashed_integration)
- events.each do |event|
- next if event == "foo"
-
- expect(project.integrations.first[event]).not_to eq(current_integration[event]),
- "expected #{!current_integration[event]} for event #{event} for service #{current_integration.title}, got #{current_integration[event]}"
- end
- end
-
- it "returns if required fields missing" do
- required_attributes = integration_attrs_list.select do |attr|
- integration_klass.validators_on(attr).any? do |v|
- v.instance_of?(ActiveRecord::Validations::PresenceValidator) &&
- # exclude presence validators with conditional since those are not really required
- ![:if, :unless].any? { |cond| v.options.include?(cond) }
- end
- end
-
- if required_attributes.empty?
- expected_code = :ok
- else
- integration_attrs.delete(required_attributes.sample)
- expected_code = :bad_request
- end
-
- put api("/projects/#{project.id}/services/#{dashed_integration}", user), params: integration_attrs
-
- expect(response).to have_gitlab_http_status(expected_code)
- end
- end
-
- describe "DELETE /projects/:id/services/#{integration.dasherize}" do
- include_context integration
-
- before do
- initialize_integration(integration)
- end
-
- it "deletes #{integration}" do
- delete api("/projects/#{project.id}/services/#{dashed_integration}", user)
-
- expect(response).to have_gitlab_http_status(:no_content)
- project.send(integration_method).reload
- expect(project.send(integration_method).activated?).to be_falsey
- end
- end
-
- describe "GET /projects/:id/services/#{integration.dasherize}" do
- include_context integration
-
- let!(:initialized_integration) { initialize_integration(integration, active: true) }
-
- let_it_be(:project2) do
- create(:project, creator_id: user.id, namespace: user.namespace)
- end
-
- def deactive_integration!
- return initialized_integration.update!(active: false) unless initialized_integration.is_a?(::Integrations::Prometheus)
-
- # Integrations::Prometheus sets `#active` itself within a `before_save`:
- initialized_integration.manual_configuration = false
- initialized_integration.save!
- end
-
- it 'returns authentication error when unauthenticated' do
- get api("/projects/#{project.id}/services/#{dashed_integration}")
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
-
- it "returns all properties of active service #{integration}" do
- get api("/projects/#{project.id}/services/#{dashed_integration}", user)
-
- expect(initialized_integration).to be_active
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names)
- end
-
- it "returns all properties of inactive integration #{integration}" do
- deactive_integration!
-
- get api("/projects/#{project.id}/services/#{dashed_integration}", user)
-
- expect(initialized_integration).not_to be_active
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties'].keys).to match_array(integration_instance.api_field_names)
- end
-
- it "returns not found if integration does not exist" do
- get api("/projects/#{project2.id}/services/#{dashed_integration}", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq('404 Service Not Found')
- end
-
- it "returns not found if service exists but is in `Project#disabled_integrations`" do
- expect_next_found_instance_of(Project) do |project|
- expect(project).to receive(:disabled_integrations).at_least(:once).and_return([integration])
- end
-
- get api("/projects/#{project.id}/services/#{dashed_integration}", user)
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq('404 Service Not Found')
- end
-
- it "returns error when authenticated but not a project owner" do
- project.add_developer(user2)
- get api("/projects/#{project.id}/services/#{dashed_integration}", user2)
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
- end
-
- describe 'POST /projects/:id/services/:slug/trigger' do
- describe 'Mattermost integration' do
- let(:integration_name) { 'mattermost_slash_commands' }
-
- context 'when no integration is available' do
- it 'returns a not found message' do
- post api("/projects/#{project.id}/services/idonotexist/trigger")
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response["error"]).to eq("404 Not Found")
- end
- end
-
- context 'when the integration exists' do
- let(:params) { { token: 'token' } }
-
- context 'when the integration is not active' do
- before do
- project.create_mattermost_slash_commands_integration(
- active: false,
- properties: params
- )
- end
-
- it 'when the integration is inactive' do
- post api("/projects/#{project.id}/services/#{integration_name}/trigger"), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when the integration is active' do
- before do
- project.create_mattermost_slash_commands_integration(
- active: true,
- properties: params
- )
- end
-
- it 'returns status 200' do
- post api("/projects/#{project.id}/services/#{integration_name}/trigger"), params: params
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'when the project can not be found' do
- it 'returns a generic 404' do
- post api("/projects/404/services/#{integration_name}/trigger"), params: params
-
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response["message"]).to eq("404 Service Not Found")
- end
- end
- end
- end
-
- describe 'Slack Integration' do
- let(:integration_name) { 'slack_slash_commands' }
-
- before do
- project.create_slack_slash_commands_integration(
- active: true,
- properties: { token: 'token' }
- )
- end
-
- it 'returns status 200' do
- post api("/projects/#{project.id}/services/#{integration_name}/trigger"), params: { token: 'token', text: 'help' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['response_type']).to eq("ephemeral")
- end
- end
- end
-
- describe 'Mattermost integration' do
- let(:integration_name) { 'mattermost' }
- let(:params) do
- { webhook: 'https://hook.example.com', username: 'username' }
- end
-
- before do
- project.create_mattermost_integration(
- active: true,
- properties: params
- )
- end
-
- it 'accepts a username for update' do
- put api("/projects/#{project.id}/services/#{integration_name}", user), params: params.merge(username: 'new_username')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']['username']).to eq('new_username')
- end
- end
-
- describe 'Microsoft Teams integration' do
- let(:integration_name) { 'microsoft-teams' }
- let(:params) do
- {
- webhook: 'https://hook.example.com',
- branches_to_be_notified: 'default',
- notify_only_broken_pipelines: false
- }
- end
-
- before do
- project.create_microsoft_teams_integration(
- active: true,
- properties: params
- )
- end
-
- it 'accepts branches_to_be_notified for update' do
- put api("/projects/#{project.id}/services/#{integration_name}", user),
- params: params.merge(branches_to_be_notified: 'all')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']['branches_to_be_notified']).to eq('all')
- end
-
- it 'accepts notify_only_broken_pipelines for update' do
- put api("/projects/#{project.id}/services/#{integration_name}", user),
- params: params.merge(notify_only_broken_pipelines: true)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
- end
- end
-
- describe 'Hangouts Chat integration' do
- let(:integration_name) { 'hangouts-chat' }
- let(:params) do
- {
- webhook: 'https://hook.example.com',
- branches_to_be_notified: 'default'
- }
- end
-
- before do
- project.create_hangouts_chat_integration(
- active: true,
- properties: params
- )
- end
-
- it 'accepts branches_to_be_notified for update', :aggregate_failures do
- put api("/projects/#{project.id}/services/#{integration_name}", user), params: params.merge(branches_to_be_notified: 'all')
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['properties']['branches_to_be_notified']).to eq('all')
- end
-
- it 'only requires the webhook param' do
- put api("/projects/#{project.id}/services/#{integration_name}", user), params: { webhook: 'https://hook.example.com' }
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- describe 'Pipelines Email Integration' do
- let(:integration_name) { 'pipelines-email' }
-
- context 'notify_only_broken_pipelines property was saved as a string' do
- before do
- project.create_pipelines_email_integration(
- active: false,
- properties: {
- "notify_only_broken_pipelines": "true",
- "branches_to_be_notified": "default"
- }
- )
- end
-
- it 'returns boolean values for notify_only_broken_pipelines' do
- get api("/projects/#{project.id}/services/#{integration_name}", user)
-
- expect(json_response['properties']['notify_only_broken_pipelines']).to eq(true)
- end
- end
- end
-end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index f5d261ba4c6..423e19c3971 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -48,6 +48,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
expect(json_response['admin_mode']).to be(false)
expect(json_response['whats_new_variant']).to eq('all_tiers')
expect(json_response['user_deactivation_emails_enabled']).to be(true)
+ expect(json_response['suggest_pipeline_enabled']).to be(true)
end
end
@@ -135,7 +136,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
wiki_page_max_content_bytes: 12345,
personal_access_token_prefix: "GL-",
user_deactivation_emails_enabled: false,
- admin_mode: true
+ admin_mode: true,
+ suggest_pipeline_enabled: false
}
expect(response).to have_gitlab_http_status(:ok)
@@ -187,6 +189,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
expect(json_response['personal_access_token_prefix']).to eq("GL-")
expect(json_response['admin_mode']).to be(true)
expect(json_response['user_deactivation_emails_enabled']).to be(false)
+ expect(json_response['suggest_pipeline_enabled']).to be(false)
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index ee1911b0a26..fb01845b63a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1457,10 +1457,20 @@ RSpec.describe API::Users do
describe "PUT /user/:id/credit_card_validation" do
let(:credit_card_validated_time) { Time.utc(2020, 1, 1) }
+ let(:expiration_year) { Date.today.year + 10 }
+ let(:params) do
+ {
+ credit_card_validated_at: credit_card_validated_time,
+ credit_card_expiration_year: expiration_year,
+ credit_card_expiration_month: 1,
+ credit_card_holder_name: 'John Smith',
+ credit_card_mask_number: '1111'
+ }
+ end
context 'when unauthenticated' do
it 'returns authentication error' do
- put api("/user/#{user.id}/credit_card_validation"), params: { credit_card_validated_at: credit_card_validated_time }
+ put api("/user/#{user.id}/credit_card_validation"), params: {}
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -1468,7 +1478,7 @@ RSpec.describe API::Users do
context 'when authenticated as non-admin' do
it "does not allow updating user's credit card validation", :aggregate_failures do
- put api("/user/#{user.id}/credit_card_validation", user), params: { credit_card_validated_at: credit_card_validated_time }
+ put api("/user/#{user.id}/credit_card_validation", user), params: params
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -1476,10 +1486,17 @@ RSpec.describe API::Users do
context 'when authenticated as admin' do
it "updates user's credit card validation", :aggregate_failures do
- put api("/user/#{user.id}/credit_card_validation", admin), params: { credit_card_validated_at: credit_card_validated_time }
+ put api("/user/#{user.id}/credit_card_validation", admin), params: params
+
+ user.reload
expect(response).to have_gitlab_http_status(:ok)
- expect(user.reload.credit_card_validated_at).to eq(credit_card_validated_time)
+ expect(user.credit_card_validation).to have_attributes(
+ credit_card_validated_at: credit_card_validated_time,
+ expiration_date: Date.new(expiration_year, 1, 31),
+ last_digits: 1111,
+ holder_name: 'John Smith'
+ )
end
it "returns 400 error if credit_card_validated_at is missing" do
@@ -1489,7 +1506,7 @@ RSpec.describe API::Users do
end
it 'returns 404 error if user not found' do
- put api("/user/#{non_existing_record_id}/credit_card_validation", admin), params: { credit_card_validated_at: credit_card_validated_time }
+ put api("/user/#{non_existing_record_id}/credit_card_validation", admin), params: params
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 User Not Found')
diff --git a/spec/requests/groups/registry/repositories_controller_spec.rb b/spec/requests/groups/registry/repositories_controller_spec.rb
index 89cbd3e4100..0699f48c2be 100644
--- a/spec/requests/groups/registry/repositories_controller_spec.rb
+++ b/spec/requests/groups/registry/repositories_controller_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe Groups::Registry::RepositoriesController do
before do
stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: :any, tags: [])
+ stub_container_registry_info
group.add_reporter(user)
login_as(user)
end
diff --git a/spec/requests/import/url_controller_spec.rb b/spec/requests/import/url_controller_spec.rb
new file mode 100644
index 00000000000..63af5e8b469
--- /dev/null
+++ b/spec/requests/import/url_controller_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Import::UrlController do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ login_as(user)
+ end
+
+ describe 'POST #validate' do
+ it 'reports success when service reports success status' do
+ allow_next_instance_of(Import::ValidateRemoteGitEndpointService) do |validate_endpoint_service|
+ allow(validate_endpoint_service).to receive(:execute).and_return(ServiceResponse.success)
+ end
+
+ post import_url_validate_path, params: { url: 'https://fake.repo' }
+
+ expect(json_response).to eq({ 'success' => true })
+ end
+
+ it 'exposes error message when service reports error' do
+ expect_next_instance_of(Import::ValidateRemoteGitEndpointService) do |validate_endpoint_service|
+ expect(validate_endpoint_service).to receive(:execute).and_return(ServiceResponse.error(message: 'foobar'))
+ end
+
+ post import_url_validate_path, params: { url: 'https://fake.repo' }
+
+ expect(json_response).to eq({ 'success' => false, 'message' => 'foobar' })
+ end
+
+ context 'with an anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it 'redirects to sign-in page' do
+ post import_url_validate_path
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects/cluster_agents_controller_spec.rb b/spec/requests/projects/cluster_agents_controller_spec.rb
new file mode 100644
index 00000000000..e4c4f537699
--- /dev/null
+++ b/spec/requests/projects/cluster_agents_controller_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::ClusterAgentsController do
+ let_it_be(:cluster_agent) { create(:cluster_agent) }
+
+ let(:project) { cluster_agent.project }
+
+ describe 'GET #show' do
+ subject { get project_cluster_agent_path(project, cluster_agent.name) }
+
+ context 'when user is unauthorized' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ subject
+ end
+
+ it 'shows 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when user is authorized' do
+ let(:user) { project.creator }
+
+ before do
+ sign_in(user)
+ subject
+ end
+
+ it 'renders content' do
+ expect(response).to be_successful
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects/google_cloud_controller_spec.rb b/spec/requests/projects/google_cloud_controller_spec.rb
new file mode 100644
index 00000000000..3b43f0d1dfb
--- /dev/null
+++ b/spec/requests/projects/google_cloud_controller_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::GoogleCloudController do
+ let_it_be(:project) { create(:project, :public) }
+
+ describe 'GET index' do
+ let_it_be(:url) { "#{project_google_cloud_index_path(project)}" }
+
+ let(:subject) { get url }
+
+ context 'when user is authorized' do
+ let(:user) { project.creator }
+
+ before do
+ sign_in(user)
+ subject
+ end
+
+ it 'renders content' do
+ expect(response).to be_successful
+ end
+ end
+
+ context 'when user is unauthorized' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_guest(user)
+ sign_in(user)
+ subject
+ end
+
+ it 'shows 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when no user is present' do
+ before do
+ subject
+ end
+
+ it 'shows 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects/merge_requests_discussions_spec.rb b/spec/requests/projects/merge_requests_discussions_spec.rb
index 8057a091bba..4921a43ab8b 100644
--- a/spec/requests/projects/merge_requests_discussions_spec.rb
+++ b/spec/requests/projects/merge_requests_discussions_spec.rb
@@ -5,11 +5,13 @@ require 'spec_helper'
RSpec.describe 'merge requests discussions' do
# Further tests can be found at merge_requests_controller_spec.rb
describe 'GET /:namespace/:project/-/merge_requests/:iid/discussions' do
- let(:project) { create(:project, :repository) }
- let(:user) { project.owner }
+ let(:project) { create(:project, :repository, :public) }
+ let(:owner) { project.owner }
+ let(:user) { create(:user) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
before do
+ project.add_maintainer(owner)
project.add_developer(user)
login_as(user)
end
@@ -232,7 +234,7 @@ RSpec.describe 'merge requests discussions' do
context 'when author role changes' do
before do
- Members::UpdateService.new(user, access_level: Gitlab::Access::GUEST).execute(author_membership)
+ Members::UpdateService.new(owner, access_level: Gitlab::Access::GUEST).execute(author_membership)
end
it_behaves_like 'cache miss' do
@@ -240,9 +242,9 @@ RSpec.describe 'merge requests discussions' do
end
end
- context 'when merge_request_discussion_cache is disabled' do
+ context 'when current_user role changes' do
before do
- stub_feature_flags(merge_request_discussion_cache: false)
+ Members::UpdateService.new(owner, access_level: Gitlab::Access::GUEST).execute(project.project_member(user))
end
it_behaves_like 'cache miss' do
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index be942f6ae86..35ce942ed7e 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -30,7 +30,11 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
throttle_unauthenticated_files_api_requests_per_period: 100,
throttle_unauthenticated_files_api_period_in_seconds: 1,
throttle_authenticated_files_api_requests_per_period: 100,
- throttle_authenticated_files_api_period_in_seconds: 1
+ throttle_authenticated_files_api_period_in_seconds: 1,
+ throttle_unauthenticated_deprecated_api_requests_per_period: 100,
+ throttle_unauthenticated_deprecated_api_period_in_seconds: 1,
+ throttle_authenticated_deprecated_api_requests_per_period: 100,
+ throttle_authenticated_deprecated_api_period_in_seconds: 1
}
end
@@ -479,6 +483,67 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
end
end
+ describe 'dependency proxy' do
+ include DependencyProxyHelpers
+
+ let_it_be_with_reload(:group) { create(:group) }
+ let_it_be_with_reload(:other_group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+
+ let(:throttle_setting_prefix) { 'throttle_authenticated_web' }
+ let(:jwt_token) { build_jwt(user) }
+ let(:other_jwt_token) { build_jwt(other_user) }
+ let(:request_args) { [path, headers: jwt_token_authorization_headers(jwt_token)] }
+ let(:other_user_request_args) { [other_path, headers: jwt_token_authorization_headers(other_jwt_token)] }
+
+ before do
+ group.add_owner(user)
+ group.create_dependency_proxy_setting!(enabled: true)
+ other_group.add_owner(other_user)
+ other_group.create_dependency_proxy_setting!(enabled: true)
+
+ allow(Gitlab.config.dependency_proxy)
+ .to receive(:enabled).and_return(true)
+ token_response = { status: :success, token: 'abcd1234' }
+ allow_next_instance_of(DependencyProxy::RequestTokenService) do |instance|
+ allow(instance).to receive(:execute).and_return(token_response)
+ end
+ end
+
+ context 'getting a manifest' do
+ let_it_be(:manifest) { create(:dependency_proxy_manifest) }
+
+ let(:path) { "/v2/#{group.path}/dependency_proxy/containers/alpine/manifests/latest" }
+ let(:other_path) { "/v2/#{other_group.path}/dependency_proxy/containers/alpine/manifests/latest" }
+ let(:pull_response) { { status: :success, manifest: manifest, from_cache: false } }
+
+ before do
+ allow_next_instance_of(DependencyProxy::FindOrCreateManifestService) do |instance|
+ allow(instance).to receive(:execute).and_return(pull_response)
+ end
+ end
+
+ it_behaves_like 'rate-limited token-authenticated requests'
+ end
+
+ context 'getting a blob' do
+ let_it_be(:blob) { create(:dependency_proxy_blob) }
+
+ let(:path) { "/v2/#{group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
+ let(:other_path) { "/v2/#{other_group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
+ let(:blob_response) { { status: :success, blob: blob, from_cache: false } }
+
+ before do
+ allow_next_instance_of(DependencyProxy::FindOrCreateBlobService) do |instance|
+ allow(instance).to receive(:execute).and_return(blob_response)
+ end
+ end
+
+ it_behaves_like 'rate-limited token-authenticated requests'
+ end
+ end
+
describe 'authenticated git lfs requests', :api do
let_it_be(:project) { create(:project, :internal) }
let_it_be(:user) { create(:user) }
@@ -790,6 +855,213 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
end
end
+ describe 'Deprecated API', :api do
+ let_it_be(:group) { create(:group, :public) }
+
+ let(:request_method) { 'GET' }
+ let(:path) { "/groups/#{group.id}" }
+ let(:params) { {} }
+
+ context 'unauthenticated' do
+ let(:throttle_setting_prefix) { 'throttle_unauthenticated_deprecated_api' }
+
+ def do_request
+ get(api(path), params: params)
+ end
+
+ before do
+ settings_to_set[:throttle_unauthenticated_deprecated_api_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_unauthenticated_deprecated_api_period_in_seconds] = period_in_seconds
+ end
+
+ context 'when unauthenticated deprecated api throttle is disabled' do
+ before do
+ settings_to_set[:throttle_unauthenticated_deprecated_api_enabled] = false
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'allows requests over the rate limit' do
+ (1 + requests_per_period).times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when unauthenticated api throttle is enabled' do
+ before do
+ settings_to_set[:throttle_unauthenticated_api_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_unauthenticated_api_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_unauthenticated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'rejects requests over the unauthenticated api rate limit' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+ end
+
+ context 'when unauthenticated web throttle is enabled' do
+ before do
+ settings_to_set[:throttle_unauthenticated_web_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_unauthenticated_web_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_unauthenticated_web_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'ignores unauthenticated web throttle' do
+ (1 + requests_per_period).times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
+
+ context 'when unauthenticated deprecated api throttle is enabled' do
+ before do
+ settings_to_set[:throttle_unauthenticated_deprecated_api_requests_per_period] = requests_per_period # 1
+ settings_to_set[:throttle_unauthenticated_deprecated_api_period_in_seconds] = period_in_seconds # 10_000
+ settings_to_set[:throttle_unauthenticated_deprecated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ context 'when group endpoint is given with_project=false' do
+ let(:params) { { with_projects: false } }
+
+ it 'permits requests over the rate limit' do
+ (1 + requests_per_period).times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ it 'rejects requests over the rate limit' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+
+ context 'when unauthenticated api throttle is lower' do
+ before do
+ settings_to_set[:throttle_unauthenticated_api_requests_per_period] = 0
+ settings_to_set[:throttle_unauthenticated_api_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_unauthenticated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'ignores unauthenticated api throttle' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+ end
+
+ it_behaves_like 'tracking when dry-run mode is set' do
+ let(:throttle_name) { 'throttle_unauthenticated_deprecated_api' }
+ end
+ end
+ end
+
+ context 'authenticated' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:member) { group.add_owner(user) }
+ let_it_be(:token) { create(:personal_access_token, user: user) }
+ let_it_be(:other_user) { create(:user) }
+ let_it_be(:other_user_token) { create(:personal_access_token, user: other_user) }
+
+ let(:throttle_setting_prefix) { 'throttle_authenticated_deprecated_api' }
+
+ before do
+ stub_application_setting(settings_to_set)
+ end
+
+ context 'with the token in the query string' do
+ let(:request_args) { [api(path, personal_access_token: token), {}] }
+ let(:other_user_request_args) { [api(path, personal_access_token: other_user_token), {}] }
+
+ it_behaves_like 'rate-limited token-authenticated requests'
+ end
+
+ context 'with the token in the headers' do
+ let(:request_args) { api_get_args_with_token_headers(path, personal_access_token_headers(token)) }
+ let(:other_user_request_args) { api_get_args_with_token_headers(path, personal_access_token_headers(other_user_token)) }
+
+ it_behaves_like 'rate-limited token-authenticated requests'
+ end
+
+ context 'precedence over authenticated api throttle' do
+ before do
+ settings_to_set[:throttle_authenticated_deprecated_api_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_authenticated_deprecated_api_period_in_seconds] = period_in_seconds
+ end
+
+ def do_request
+ get(api(path, personal_access_token: token), params: params)
+ end
+
+ context 'when authenticated deprecated api throttle is enabled' do
+ before do
+ settings_to_set[:throttle_authenticated_deprecated_api_enabled] = true
+ end
+
+ context 'when authenticated api throttle is lower' do
+ before do
+ settings_to_set[:throttle_authenticated_api_requests_per_period] = 0
+ settings_to_set[:throttle_authenticated_api_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_authenticated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'ignores authenticated api throttle' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+ end
+ end
+
+ context 'when authenticated deprecated api throttle is disabled' do
+ before do
+ settings_to_set[:throttle_authenticated_deprecated_api_enabled] = false
+ end
+
+ context 'when authenticated api throttle is enabled' do
+ before do
+ settings_to_set[:throttle_authenticated_api_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_authenticated_api_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_authenticated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'rejects requests over the authenticated api rate limit' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+ end
+ end
+ end
+ end
+ end
+
describe 'throttle bypass header' do
let(:headers) { {} }
let(:bypass_header) { 'gitlab-bypass-rate-limiting' }