summaryrefslogtreecommitdiff
path: root/spec/requests/api
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests/api')
-rw-r--r--spec/requests/api/api_spec.rb24
-rw-r--r--spec/requests/api/ci/jobs_spec.rb173
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb6
-rw-r--r--spec/requests/api/debian_group_packages_spec.rb15
-rw-r--r--spec/requests/api/debian_project_packages_spec.rb21
-rw-r--r--spec/requests/api/deploy_keys_spec.rb54
-rw-r--r--spec/requests/api/error_tracking/collector_spec.rb51
-rw-r--r--spec/requests/api/features_spec.rb30
-rw-r--r--spec/requests/api/files_spec.rb44
-rw-r--r--spec/requests/api/generic_packages_spec.rb31
-rw-r--r--spec/requests/api/graphql/ci/pipelines_spec.rb63
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb23
-rw-r--r--spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb22
-rw-r--r--spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/design_management/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/create_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/issues/move_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb161
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_severity_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb (renamed from spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb)8
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/update_reviewer_state_spec.rb65
-rw-r--r--spec/requests/api/graphql/mutations/releases/create_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/releases/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/releases/update_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb26
-rw-r--r--spec/requests/api/graphql/namespace_query_spec.rb86
-rw-r--r--spec/requests/api/graphql/packages/helm_spec.rb59
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb16
-rw-r--r--spec/requests/api/graphql/project/release_spec.rb186
-rw-r--r--spec/requests/api/graphql/project/releases_spec.rb15
-rw-r--r--spec/requests/api/graphql_spec.rb30
-rw-r--r--spec/requests/api/group_debian_distributions_spec.rb16
-rw-r--r--spec/requests/api/groups_spec.rb7
-rw-r--r--spec/requests/api/internal/base_spec.rb2
-rw-r--r--spec/requests/api/invitations_spec.rb32
-rw-r--r--spec/requests/api/lint_spec.rb123
-rw-r--r--spec/requests/api/members_spec.rb54
-rw-r--r--spec/requests/api/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/namespaces_spec.rb76
-rw-r--r--spec/requests/api/npm_project_packages_spec.rb20
-rw-r--r--spec/requests/api/project_attributes.yml1
-rw-r--r--spec/requests/api/project_debian_distributions_spec.rb22
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/requests/api/project_snapshots_spec.rb1
-rw-r--r--spec/requests/api/project_snippets_spec.rb1
-rw-r--r--spec/requests/api/projects_spec.rb31
-rw-r--r--spec/requests/api/releases_spec.rb32
-rw-r--r--spec/requests/api/repositories_spec.rb39
-rw-r--r--spec/requests/api/settings_spec.rb41
-rw-r--r--spec/requests/api/snippets_spec.rb1
-rw-r--r--spec/requests/api/tags_spec.rb67
-rw-r--r--spec/requests/api/terraform/modules/v1/packages_spec.rb17
-rw-r--r--spec/requests/api/todos_spec.rb12
-rw-r--r--spec/requests/api/topics_spec.rb217
-rw-r--r--spec/requests/api/users_spec.rb8
-rw-r--r--spec/requests/api/v3/github_spec.rb131
59 files changed, 2022 insertions, 170 deletions
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index 95eb503c6bc..6a02f81fcae 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -116,7 +116,7 @@ RSpec.describe API::API do
'meta.root_namespace' => project.namespace.full_path,
'meta.user' => user.username,
'meta.client_id' => a_string_matching(%r{\Auser/.+}),
- 'meta.feature_category' => 'issue_tracking',
+ 'meta.feature_category' => 'team_planning',
'route' => '/api/:version/projects/:id/issues')
end
@@ -200,6 +200,28 @@ RSpec.describe API::API do
expect(response).to have_gitlab_http_status(:not_found)
end
end
+
+ context 'when there is an unhandled exception for an anonymous request' 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/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
+
+ expect(BroadcastMessage).to receive(:all).and_raise('An error!')
+
+ get(api('/broadcast_messages'))
+
+ expect(response).to have_gitlab_http_status(:internal_server_error)
+ end
+ end
end
describe 'Marginalia comments' do
diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb
index b6ab9310471..410020b68cd 100644
--- a/spec/requests/api/ci/jobs_spec.rb
+++ b/spec/requests/api/ci/jobs_spec.rb
@@ -176,6 +176,111 @@ RSpec.describe API::Ci::Jobs do
end
end
+ describe 'GET /job/allowed_agents' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group_agent) { create(:cluster_agent, project: create(:project, group: group)) }
+ let_it_be(:group_authorization) { create(:agent_group_authorization, agent: group_agent, group: group) }
+ let_it_be(:project_agent) { create(:cluster_agent, project: project) }
+
+ before(:all) do
+ project.update!(group: group_authorization.group)
+ end
+
+ let(:implicit_authorization) { Clusters::Agents::ImplicitAuthorization.new(agent: project_agent) }
+
+ let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token } }
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user, status: job_status) }
+ let(:job_status) { 'running' }
+ let(:params) { {} }
+
+ subject do
+ get api('/job/allowed_agents'), headers: headers, params: params
+ end
+
+ before do
+ subject
+ end
+
+ context 'when token is valid and user is authorized' do
+ shared_examples_for 'valid allowed_agents request' do
+ it 'returns agent info', :aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+
+ expect(json_response.dig('job', 'id')).to eq(job.id)
+ expect(json_response.dig('pipeline', 'id')).to eq(job.pipeline_id)
+ expect(json_response.dig('project', 'id')).to eq(job.project_id)
+ expect(json_response.dig('project', 'groups')).to match_array([{ 'id' => group_authorization.group.id }])
+ expect(json_response.dig('user', 'id')).to eq(api_user.id)
+ expect(json_response.dig('user', 'username')).to eq(api_user.username)
+ expect(json_response.dig('user', 'roles_in_project')).to match_array %w(guest reporter developer)
+ expect(json_response).not_to include('environment')
+ expect(json_response['allowed_agents']).to match_array([
+ {
+ 'id' => implicit_authorization.agent_id,
+ 'config_project' => hash_including('id' => implicit_authorization.agent.project_id),
+ 'configuration' => implicit_authorization.config
+ },
+ {
+ 'id' => group_authorization.agent_id,
+ 'config_project' => hash_including('id' => group_authorization.agent.project_id),
+ 'configuration' => group_authorization.config
+ }
+ ])
+ end
+ end
+
+ it_behaves_like 'valid allowed_agents request'
+
+ context 'when deployment' do
+ let(:job) { create(:ci_build, :artifacts, :with_deployment, environment: 'production', pipeline: pipeline, user: api_user, status: job_status) }
+
+ it 'includes environment slug' do
+ expect(json_response.dig('environment', 'slug')).to eq('production')
+ end
+ end
+
+ context 'when passing the token as params' do
+ let(:headers) { {} }
+ let(:params) { { job_token: job.token } }
+
+ it_behaves_like 'valid allowed_agents request'
+ end
+ end
+
+ context 'when user is anonymous' do
+ let(:api_user) { nil }
+
+ it 'returns unauthorized' do
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when token is invalid because job has finished' do
+ let(:job_status) { 'success' }
+
+ it 'returns unauthorized' do
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when token is invalid' do
+ let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => 'bad_token' } }
+
+ it 'returns unauthorized' do
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'when token is valid but not CI_JOB_TOKEN' do
+ let(:token) { create(:personal_access_token, user: user) }
+ let(:headers) { { 'Private-Token' => token.token } }
+
+ it 'returns not found' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
describe 'GET /projects/:id/jobs' do
let(:query) { {} }
@@ -203,6 +308,7 @@ RSpec.describe API::Ci::Jobs do
it 'returns no artifacts nor trace data' do
json_job = json_response.first
+ expect(response).to have_gitlab_http_status(:ok)
expect(json_job['artifacts_file']).to be_nil
expect(json_job['artifacts']).to be_an Array
expect(json_job['artifacts']).to be_empty
@@ -321,6 +427,22 @@ RSpec.describe API::Ci::Jobs do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
+
+ context 'when trace artifact record exists with no stored file', :skip_before_request do
+ before do
+ create(:ci_job_artifact, :unarchived_trace_artifact, job: job, project: job.project)
+ end
+
+ it 'returns no artifacts nor trace data' do
+ get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['artifacts']).to be_an Array
+ expect(json_response['artifacts'].size).to eq(1)
+ expect(json_response['artifacts'][0]['file_type']).to eq('trace')
+ expect(json_response['artifacts'][0]['filename']).to eq('job.log')
+ end
+ end
end
describe 'DELETE /projects/:id/jobs/:job_id/artifacts' do
@@ -456,6 +578,7 @@ RSpec.describe API::Ci::Jobs do
expect(response.headers.to_h)
.to include('Content-Type' => 'application/json',
'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ expect(response.parsed_body).to be_empty
end
context 'when artifacts are locked' do
@@ -826,6 +949,7 @@ RSpec.describe API::Ci::Jobs do
expect(response.headers.to_h)
.to include('Content-Type' => 'application/json',
'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ expect(response.parsed_body).to be_empty
end
end
@@ -919,7 +1043,16 @@ RSpec.describe API::Ci::Jobs do
end
end
- context 'when trace is file' do
+ context 'when live trace and uploadless trace artifact' do
+ let(:job) { create(:ci_build, :trace_live, :unarchived_trace_artifact, pipeline: pipeline) }
+
+ it 'returns specific job trace' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(job.trace.raw)
+ end
+ end
+
+ context 'when trace is live' do
let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
it 'returns specific job trace' do
@@ -927,6 +1060,28 @@ RSpec.describe API::Ci::Jobs do
expect(response.body).to eq(job.trace.raw)
end
end
+
+ context 'when no trace' do
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'returns empty trace' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to be_empty
+ end
+ end
+
+ context 'when trace artifact record exists with no stored file' do
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ before do
+ create(:ci_job_artifact, :unarchived_trace_artifact, job: job, project: job.project)
+ end
+
+ it 'returns empty trace' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to be_empty
+ end
+ end
end
context 'unauthorized user' do
@@ -1038,9 +1193,7 @@ RSpec.describe API::Ci::Jobs do
post api("/projects/#{project.id}/jobs/#{job.id}/erase", user)
end
- context 'job is erasable' do
- let(:job) { create(:ci_build, :trace_artifact, :artifacts, :test_reports, :success, project: project, pipeline: pipeline) }
-
+ shared_examples_for 'erases job' do
it 'erases job content' do
expect(response).to have_gitlab_http_status(:created)
expect(job.job_artifacts.count).to eq(0)
@@ -1049,6 +1202,12 @@ RSpec.describe API::Ci::Jobs do
expect(job.artifacts_metadata.present?).to be_falsy
expect(job.has_job_artifacts?).to be_falsy
end
+ end
+
+ context 'job is erasable' do
+ let(:job) { create(:ci_build, :trace_artifact, :artifacts, :test_reports, :success, project: project, pipeline: pipeline) }
+
+ it_behaves_like 'erases job'
it 'updates job' do
job.reload
@@ -1058,6 +1217,12 @@ RSpec.describe API::Ci::Jobs do
end
end
+ context 'when job has an unarchived trace artifact' do
+ let(:job) { create(:ci_build, :success, :trace_live, :unarchived_trace_artifact, project: project, pipeline: pipeline) }
+
+ it_behaves_like 'erases job'
+ end
+
context 'job is not erasable' do
let(:job) { create(:ci_build, :trace_live, project: project, pipeline: pipeline) }
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 c3fbef9be48..fdf1a278d4c 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -218,9 +218,11 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(json_response['git_info']).to eq(expected_git_info)
expect(json_response['image']).to eq({ 'name' => 'ruby:2.7', 'entrypoint' => '/bin/sh', 'ports' => [] })
expect(json_response['services']).to eq([{ 'name' => 'postgres', 'entrypoint' => nil,
- 'alias' => nil, 'command' => nil, 'ports' => [] },
+ 'alias' => nil, 'command' => nil, 'ports' => [], 'variables' => nil },
{ 'name' => 'docker:stable-dind', 'entrypoint' => '/bin/sh',
- 'alias' => 'docker', 'command' => 'sleep 30', 'ports' => [] }])
+ 'alias' => 'docker', 'command' => 'sleep 30', 'ports' => [], 'variables' => [] },
+ { 'name' => 'mysql:latest', 'entrypoint' => nil,
+ 'alias' => nil, 'command' => nil, 'ports' => [], 'variables' => [{ 'key' => 'MYSQL_ROOT_PASSWORD', 'value' => 'root123.' }] }])
expect(json_response['steps']).to eq(expected_steps)
expect(json_response['artifacts']).to eq(expected_artifacts)
expect(json_response['cache']).to eq(expected_cache)
diff --git a/spec/requests/api/debian_group_packages_spec.rb b/spec/requests/api/debian_group_packages_spec.rb
index 3e11b480860..d881d4350fb 100644
--- a/spec/requests/api/debian_group_packages_spec.rb
+++ b/spec/requests/api/debian_group_packages_spec.rb
@@ -9,35 +9,36 @@ RSpec.describe API::DebianGroupPackages do
context 'with invalid parameter' do
let(:url) { "/groups/1/-/packages/debian/dists/with+space/InRelease" }
- it_behaves_like 'Debian repository GET request', :bad_request, /^distribution is invalid$/
+ it_behaves_like 'Debian packages GET request', :bad_request, /^distribution is invalid$/
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release.gpg' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release.gpg" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^-----BEGIN PGP SIGNATURE-----/
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNATURE-----/
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/Release' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/Release" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^Codename: fixture-distribution\n$/
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Codename: fixture-distribution\n$/
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/InRelease' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/InRelease" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/
end
describe 'GET groups/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do
let(:url) { "/groups/#{container.id}/-/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/Packages" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Packages file/
end
describe 'GET groups/:id/-/packages/debian/pool/:codename/:project_id/:letter/:package_name/:package_version/:file_name' do
let(:url) { "/groups/#{container.id}/-/packages/debian/pool/#{package.debian_distribution.codename}/#{project.id}/#{letter}/#{package.name}/#{package.version}/#{file_name}" }
+ let(:file_name) { params[:file_name] }
using RSpec::Parameterized::TableSyntax
@@ -51,9 +52,7 @@ RSpec.describe API::DebianGroupPackages do
end
with_them do
- include_context 'with file_name', params[:file_name]
-
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, params[:success_body]
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, params[:success_body]
end
end
end
diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb
index d0b0debaf13..bd68bf912e1 100644
--- a/spec/requests/api/debian_project_packages_spec.rb
+++ b/spec/requests/api/debian_project_packages_spec.rb
@@ -9,35 +9,36 @@ RSpec.describe API::DebianProjectPackages do
context 'with invalid parameter' do
let(:url) { "/projects/1/packages/debian/dists/with+space/InRelease" }
- it_behaves_like 'Debian repository GET request', :bad_request, /^distribution is invalid$/
+ it_behaves_like 'Debian packages GET request', :bad_request, /^distribution is invalid$/
end
describe 'GET projects/:id/packages/debian/dists/*distribution/Release.gpg' do
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release.gpg" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^-----BEGIN PGP SIGNATURE-----/
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNATURE-----/
end
describe 'GET projects/:id/packages/debian/dists/*distribution/Release' do
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/Release" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^Codename: fixture-distribution\n$/
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^Codename: fixture-distribution\n$/
end
describe 'GET projects/:id/packages/debian/dists/*distribution/InRelease' do
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/InRelease" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, /^-----BEGIN PGP SIGNED MESSAGE-----/
end
describe 'GET projects/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages' do
let(:url) { "/projects/#{container.id}/packages/debian/dists/#{distribution.codename}/#{component.name}/binary-#{architecture.name}/Packages" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /Description: This is an incomplete Packages file/
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, /Description: This is an incomplete Packages file/
end
describe 'GET projects/:id/packages/debian/pool/:codename/:letter/:package_name/:package_version/:file_name' do
let(:url) { "/projects/#{container.id}/packages/debian/pool/#{package.debian_distribution.codename}/#{letter}/#{package.name}/#{package.version}/#{file_name}" }
+ let(:file_name) { params[:file_name] }
using RSpec::Parameterized::TableSyntax
@@ -51,9 +52,7 @@ RSpec.describe API::DebianProjectPackages do
end
with_them do
- include_context 'with file_name', params[:file_name]
-
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, params[:success_body]
+ it_behaves_like 'Debian packages read endpoint', 'GET', :success, params[:success_body]
end
end
@@ -65,13 +64,13 @@ RSpec.describe API::DebianProjectPackages do
context 'with a deb' do
let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' }
- it_behaves_like 'Debian repository write endpoint', 'upload request', :created
+ it_behaves_like 'Debian packages write endpoint', 'upload', :created, nil
end
context 'with a changes file' do
let(:file_name) { 'sample_1.2.3~alpha2_amd64.changes' }
- it_behaves_like 'Debian repository write endpoint', 'upload request', :created
+ it_behaves_like 'Debian packages write endpoint', 'upload', :created, nil
end
end
@@ -80,7 +79,7 @@ RSpec.describe API::DebianProjectPackages do
let(:method) { :put }
let(:url) { "/projects/#{container.id}/packages/debian/#{file_name}/authorize" }
- it_behaves_like 'Debian repository write endpoint', 'upload authorize request', :created
+ it_behaves_like 'Debian packages write endpoint', 'upload authorize', :created, nil
end
end
end
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index a01c66a311c..1daa7c38e04 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -8,8 +8,9 @@ RSpec.describe API::DeployKeys do
let_it_be(:admin) { create(:admin) }
let_it_be(:project) { create(:project, creator_id: user.id) }
let_it_be(:project2) { create(:project, creator_id: user.id) }
-
- let(:deploy_key) { create(:deploy_key, public: true) }
+ let_it_be(:project3) { create(:project, creator_id: user.id) }
+ let_it_be(:deploy_key) { create(:deploy_key, public: true) }
+ let_it_be(:deploy_key_private) { create(:deploy_key, public: false) }
let!(:deploy_keys_project) do
create(:deploy_keys_project, project: project, deploy_key: deploy_key)
@@ -33,13 +34,56 @@ RSpec.describe API::DeployKeys do
end
context 'when authenticated as admin' do
+ let_it_be(:pat) { create(:personal_access_token, user: admin) }
+
+ def make_api_request(params = {})
+ get api('/deploy_keys', personal_access_token: pat), params: params
+ end
+
it 'returns all deploy keys' do
- get api('/deploy_keys', admin)
+ make_api_request
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
+ expect(response).to match_response_schema('public_api/v4/deploy_keys')
expect(json_response).to be_an Array
- expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
+
+ expect(json_response[0]['id']).to eq(deploy_key.id)
+ expect(json_response[1]['id']).to eq(deploy_key_private.id)
+ end
+
+ it 'avoids N+1 database queries', :use_sql_query_cache, :request_store do
+ create(:deploy_keys_project, :write_access, project: project2, deploy_key: deploy_key)
+
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) { make_api_request }
+
+ deploy_key2 = create(:deploy_key, public: true)
+ create(:deploy_keys_project, :write_access, project: project3, deploy_key: deploy_key2)
+
+ expect { make_api_request }.not_to exceed_all_query_limit(control)
+ end
+
+ context 'when `public` parameter is `true`' do
+ it 'only returns public deploy keys' do
+ make_api_request({ public: true })
+
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(deploy_key.id)
+ end
+ end
+
+ context 'projects_with_write_access' do
+ let!(:deploy_keys_project2) { create(:deploy_keys_project, :write_access, project: project2, deploy_key: deploy_key) }
+ let!(:deploy_keys_project3) { create(:deploy_keys_project, :write_access, project: project3, deploy_key: deploy_key) }
+
+ it 'returns projects with write access' do
+ make_api_request
+
+ response_projects_with_write_access = json_response.first['projects_with_write_access']
+
+ expect(response_projects_with_write_access[0]['id']).to eq(project2.id)
+ expect(response_projects_with_write_access[1]['id']).to eq(project3.id)
+ end
end
end
end
@@ -58,6 +102,7 @@ RSpec.describe API::DeployKeys do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(deploy_key.title)
+ expect(json_response.first).not_to have_key(:projects_with_write_access)
end
it 'returns multiple deploy keys without N + 1' do
@@ -77,6 +122,7 @@ RSpec.describe API::DeployKeys do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['title']).to eq(deploy_key.title)
+ expect(json_response).not_to have_key(:projects_with_write_access)
end
it 'returns 404 Not Found with invalid ID' do
diff --git a/spec/requests/api/error_tracking/collector_spec.rb b/spec/requests/api/error_tracking/collector_spec.rb
index 7acadeb1287..21e2849fef0 100644
--- a/spec/requests/api/error_tracking/collector_spec.rb
+++ b/spec/requests/api/error_tracking/collector_spec.rb
@@ -24,10 +24,10 @@ RSpec.describe API::ErrorTracking::Collector do
end
RSpec.shared_examples 'successful request' do
- it 'writes to the database and returns no content' do
+ it 'writes to the database and returns OK' do
expect { subject }.to change { ErrorTracking::ErrorEvent.count }.by(1)
- expect(response).to have_gitlab_http_status(:no_content)
+ expect(response).to have_gitlab_http_status(:ok)
end
end
@@ -89,13 +89,27 @@ RSpec.describe API::ErrorTracking::Collector do
context 'transaction request type' do
let(:params) { fixture_file('error_tracking/transaction.txt') }
- it 'does nothing and returns no content' do
+ it 'does nothing and returns ok' do
expect { subject }.not_to change { ErrorTracking::ErrorEvent.count }
- expect(response).to have_gitlab_http_status(:no_content)
+ expect(response).to have_gitlab_http_status(:ok)
end
end
+ context 'gzip body' do
+ let(:headers) do
+ {
+ 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}",
+ 'HTTP_CONTENT_ENCODING' => 'gzip',
+ 'CONTENT_TYPE' => 'application/x-sentry-envelope'
+ }
+ end
+
+ let(:params) { ActiveSupport::Gzip.compress(raw_event) }
+
+ it_behaves_like 'successful request'
+ end
+
it_behaves_like 'successful request'
end
@@ -122,6 +136,35 @@ RSpec.describe API::ErrorTracking::Collector do
it_behaves_like 'bad request'
end
+ context 'body with string instead of json' do
+ let(:params) { '"********"' }
+
+ it_behaves_like 'bad request'
+ end
+
+ context 'collector fails with validation error' do
+ before do
+ allow(::ErrorTracking::CollectErrorService)
+ .to receive(:new).and_raise(ActiveRecord::RecordInvalid)
+ end
+
+ it_behaves_like 'bad request'
+ end
+
+ context 'gzip body' do
+ let(:headers) do
+ {
+ 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}",
+ 'HTTP_CONTENT_ENCODING' => 'gzip',
+ 'CONTENT_TYPE' => 'application/json'
+ }
+ end
+
+ let(:params) { ActiveSupport::Gzip.compress(raw_event) }
+
+ it_behaves_like 'successful request'
+ end
+
context 'sentry_key as param and empty headers' do
let(:url) { "/error_tracking/collector/api/#{project.id}/store?sentry_key=#{sentry_key}" }
let(:headers) { {} }
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 0e163ec2154..35dba93b766 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -256,6 +256,21 @@ RSpec.describe API::Features, stub_feature_flags: false do
)
end
+ it 'creates a feature with the given percentage of time if passed a float' do
+ post api("/features/#{feature_name}", admin), params: { value: '0.01' }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to match(
+ 'name' => feature_name,
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'percentage_of_time', 'value' => 0.01 }
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
+ end
+
it 'creates a feature with the given percentage of actors if passed an integer' do
post api("/features/#{feature_name}", admin), params: { value: '50', key: 'percentage_of_actors' }
@@ -270,6 +285,21 @@ RSpec.describe API::Features, stub_feature_flags: false do
'definition' => known_feature_flag_definition_hash
)
end
+
+ it 'creates a feature with the given percentage of actors if passed a float' do
+ post api("/features/#{feature_name}", admin), params: { value: '0.01', key: 'percentage_of_actors' }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response).to match(
+ 'name' => feature_name,
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'percentage_of_actors', 'value' => 0.01 }
+ ],
+ 'definition' => known_feature_flag_definition_hash
+ )
+ end
end
context 'when the feature exists' do
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 0b898496dd6..6aa12b6ff48 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -47,6 +47,15 @@ RSpec.describe API::Files do
"/projects/#{project.id}/repository/files/#{file_path}"
end
+ def expect_to_send_git_blob(url, params)
+ expect(Gitlab::Workhorse).to receive(:send_git_blob)
+
+ get url, params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.parsed_body).to be_empty
+ end
+
context 'http headers' do
it 'converts value into string' do
helper.set_http_headers(test: 1)
@@ -257,11 +266,7 @@ RSpec.describe API::Files do
it 'returns raw file info' do
url = route(file_path) + "/raw"
- expect(Gitlab::Workhorse).to receive(:send_git_blob)
-
- get api(url, api_user, **options), params: params
-
- expect(response).to have_gitlab_http_status(:ok)
+ expect_to_send_git_blob(api(url, api_user, **options), params)
expect(headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
@@ -523,11 +528,8 @@ RSpec.describe API::Files do
it 'returns raw file info' do
url = route(file_path) + "/raw"
- expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(url, current_user), params: params
-
- expect(response).to have_gitlab_http_status(:ok)
+ expect_to_send_git_blob(api(url, current_user), params)
end
context 'when ref is not provided' do
@@ -537,39 +539,29 @@ RSpec.describe API::Files do
it 'returns response :ok', :aggregate_failures do
url = route(file_path) + "/raw"
- expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(url, current_user), params: {}
-
- expect(response).to have_gitlab_http_status(:ok)
+ expect_to_send_git_blob(api(url, current_user), {})
end
end
it 'returns raw file info for files with dots' do
url = route('.gitignore') + "/raw"
- expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(url, current_user), params: params
-
- expect(response).to have_gitlab_http_status(:ok)
+ expect_to_send_git_blob(api(url, current_user), params)
end
it 'returns file by commit sha' do
# This file is deleted on HEAD
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
- expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(route(file_path) + "/raw", current_user), params: params
-
- expect(response).to have_gitlab_http_status(:ok)
+ expect_to_send_git_blob(api(route(file_path) + "/raw", current_user), params)
end
it 'sets no-cache headers' do
url = route('.gitignore') + "/raw"
- expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(url, current_user), params: params
+ expect_to_send_git_blob(api(url, current_user), params)
expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate, no-store, no-cache")
expect(response.headers["Pragma"]).to eq("no-cache")
@@ -633,11 +625,9 @@ RSpec.describe API::Files do
# This file is deleted on HEAD
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
- expect(Gitlab::Workhorse).to receive(:send_git_blob)
+ url = api(route(file_path) + "/raw", personal_access_token: token)
- get api(route(file_path) + "/raw", personal_access_token: token), params: params
-
- expect(response).to have_gitlab_http_status(:ok)
+ expect_to_send_git_blob(url, params)
end
end
end
diff --git a/spec/requests/api/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb
index 7e439a22e4b..2d85d7b9583 100644
--- a/spec/requests/api/generic_packages_spec.rb
+++ b/spec/requests/api/generic_packages_spec.rb
@@ -297,6 +297,37 @@ RSpec.describe API::GenericPackages do
end
end
+ context 'with select' do
+ context 'with a valid value' do
+ context 'package_file' do
+ let(:params) { super().merge(select: 'package_file') }
+
+ it 'returns a package file' do
+ headers = workhorse_headers.merge(auth_header)
+
+ upload_file(params, headers)
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to have_key('id')
+ end
+ end
+ end
+ end
+
+ context 'with an invalid value' do
+ let(:params) { super().merge(select: 'invalid_value') }
+
+ it 'returns a package file' do
+ headers = workhorse_headers.merge(auth_header)
+
+ upload_file(params, headers)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+
context 'with a status' do
context 'valid status' do
let(:params) { super().merge(status: 'hidden') }
diff --git a/spec/requests/api/graphql/ci/pipelines_spec.rb b/spec/requests/api/graphql/ci/pipelines_spec.rb
index 6587061094d..1f47f678898 100644
--- a/spec/requests/api/graphql/ci/pipelines_spec.rb
+++ b/spec/requests/api/graphql/ci/pipelines_spec.rb
@@ -186,6 +186,69 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
end
end
+ describe '.job_artifacts' do
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:pipeline_job_1) { create(:ci_build, pipeline: pipeline, name: 'Job 1') }
+ let_it_be(:pipeline_job_artifact_1) { create(:ci_job_artifact, job: pipeline_job_1) }
+ let_it_be(:pipeline_job_2) { create(:ci_build, pipeline: pipeline, name: 'Job 2') }
+ let_it_be(:pipeline_job_artifact_2) { create(:ci_job_artifact, job: pipeline_job_2) }
+
+ let(:path) { %i[project pipelines nodes jobArtifacts] }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ pipelines {
+ nodes {
+ jobArtifacts {
+ name
+ downloadPath
+ fileType
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ before do
+ post_graphql(query, current_user: user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns the job_artifacts of a pipeline' do
+ job_artifacts_graphql_data = graphql_data_at(*path).flatten
+
+ expect(
+ job_artifacts_graphql_data.map { |pip| pip['name'] }
+ ).to contain_exactly(pipeline_job_artifact_1.filename, pipeline_job_artifact_2.filename)
+ end
+
+ it 'avoids N+1 queries' do
+ first_user = create(:user)
+ second_user = create(:user)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ post_graphql(query, current_user: first_user)
+ end
+
+ pipeline_2 = create(:ci_pipeline, project: project)
+ pipeline_2_job_1 = create(:ci_build, pipeline: pipeline_2, name: 'Pipeline 2 Job 1')
+ create(:ci_job_artifact, job: pipeline_2_job_1)
+ pipeline_2_job_2 = create(:ci_build, pipeline: pipeline_2, name: 'Pipeline 2 Job 2')
+ create(:ci_job_artifact, job: pipeline_2_job_2)
+
+ expect do
+ post_graphql(query, current_user: second_user)
+ end.not_to exceed_query_limit(control_count)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
describe '.jobs(securityReportTypes)' do
let_it_be(:query) do
%(
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index b41d851439b..8bbeae97f57 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -190,19 +190,18 @@ RSpec.describe 'GitlabSchema configurations' do
let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/introspection.graphql')) }
it 'logs the query complexity and depth' do
- analyzer_memo = {
- query_string: query,
- variables: {}.to_s,
- complexity: 181,
- depth: 13,
- duration_s: 7,
- operation_name: 'IntrospectionQuery',
- used_fields: an_instance_of(Array),
- used_deprecated_fields: an_instance_of(Array)
- }
-
expect_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:duration).and_return(7)
- expect(Gitlab::GraphqlLogger).to receive(:info).with(analyzer_memo)
+
+ expect(Gitlab::GraphqlLogger).to receive(:info).with(
+ hash_including(
+ trace_type: 'execute_query',
+ "query_analysis.duration_s" => 7,
+ "query_analysis.complexity" => 181,
+ "query_analysis.depth" => 13,
+ "query_analysis.used_deprecated_fields" => an_instance_of(Array),
+ "query_analysis.used_fields" => an_instance_of(Array)
+ )
+ )
post_graphql(query, current_user: nil)
end
diff --git a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb
index 30e704adb92..3527c8183f6 100644
--- a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb
+++ b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb
@@ -116,4 +116,26 @@ RSpec.describe 'getting dependency proxy manifests in a group' do
expect(dependency_proxy_image_count_response).to eq(manifests.size)
end
+
+ describe 'sorting and pagination' do
+ let(:data_path) { ['group', :dependencyProxyManifests] }
+ let(:current_user) { owner }
+
+ context 'with default sorting' do
+ let_it_be(:descending_manifests) { manifests.reverse.map { |manifest| global_id_of(manifest)} }
+
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { '' }
+ let(:first_param) { 2 }
+ let(:all_records) { descending_manifests }
+ end
+ end
+
+ def pagination_query(params)
+ # remove sort since the type does not accept sorting, but be future proof
+ graphql_query_for('group', { 'fullPath' => group.full_path },
+ query_nodes(:dependencyProxyManifests, :id, include_pagination_info: true, args: params.merge(sort: nil))
+ )
+ end
+ 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 0fd8fdc3f59..322706be119 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
@@ -15,7 +15,7 @@ RSpec.describe 'RunnersRegistrationTokenReset' do
subject
expect(graphql_errors).not_to be_empty
- expect(graphql_errors).to include(a_hash_including('message' => "The resource that you are attempting to access does not exist or you don't have permission to perform this action"))
+ expect(graphql_errors).to include(a_hash_including('message' => Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR))
expect(mutation_response).to be_nil
end
end
diff --git a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
index e329416faee..1dffb86b344 100644
--- a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
@@ -53,7 +53,7 @@ RSpec.describe "deleting designs" do
context 'the designs list contains filenames we cannot find' do
it_behaves_like 'a failed request' do
- let(:designs) { %w/foo bar baz/.map { |fn| OpenStruct.new(filename: fn) } }
+ let(:designs) { %w/foo bar baz/.map { |fn| instance_double('file', filename: fn) } }
let(:the_error) { a_string_matching %r/filenames were not found/ }
end
end
diff --git a/spec/requests/api/graphql/mutations/issues/create_spec.rb b/spec/requests/api/graphql/mutations/issues/create_spec.rb
index 886f3140086..6baed352b37 100644
--- a/spec/requests/api/graphql/mutations/issues/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/create_spec.rb
@@ -48,5 +48,9 @@ RSpec.describe 'Create an issue' do
expect(mutation_response['issue']).to include('discussionLocked' => true)
expect(Issue.last.work_item_type.base_type).to eq('issue')
end
+
+ it_behaves_like 'has spam protection' do
+ let(:mutation_class) { ::Mutations::Issues::Create }
+ end
end
end
diff --git a/spec/requests/api/graphql/mutations/issues/move_spec.rb b/spec/requests/api/graphql/mutations/issues/move_spec.rb
index 5bbaff61edd..20ed16879f6 100644
--- a/spec/requests/api/graphql/mutations/issues/move_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/move_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe 'Moving an issue' do
context 'when the user is not allowed to read source project' do
it 'returns an error' do
- error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
post_graphql_mutation(mutation, current_user: user)
expect(response).to have_gitlab_http_status(:success)
diff --git a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
index 3f804a46992..12ab504da14 100644
--- a/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_confidential_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe 'Setting an issue as confidential' do
end
it 'returns an error if the user is not allowed to update the issue' do
- error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
post_graphql_mutation(mutation, current_user: create(:user))
expect(graphql_errors).to include(a_hash_including('message' => error))
diff --git a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
new file mode 100644
index 00000000000..3da702c55d7
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Setting issues crm contacts' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:contacts) { create_list(:contact, 4, group: group) }
+
+ let(:issue) { create(:issue, project: project) }
+ let(:operation_mode) { Types::MutationOperationModeEnum.default_mode }
+ let(:crm_contact_ids) { [global_id_of(contacts[1]), global_id_of(contacts[2])] }
+ let(:does_not_exist_or_no_permission) { "The resource that you are attempting to access does not exist or you don't have permission to perform this action" }
+
+ let(:mutation) do
+ variables = {
+ project_path: issue.project.full_path,
+ iid: issue.iid.to_s,
+ operation_mode: operation_mode,
+ crm_contact_ids: crm_contact_ids
+ }
+
+ graphql_mutation(:issue_set_crm_contacts, variables,
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ customerRelationsContacts {
+ nodes {
+ id
+ }
+ }
+ }
+ QL
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:issue_set_crm_contacts)
+ end
+
+ before do
+ create(:issue_customer_relations_contact, issue: issue, contact: contacts[0])
+ create(:issue_customer_relations_contact, issue: issue, contact: contacts[1])
+ end
+
+ context 'when the user has no permission' do
+ it 'returns expected error' do
+ error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_errors).to include(a_hash_including('message' => error))
+ end
+ end
+
+ context 'when the user has permission' do
+ before do
+ group.add_reporter(user)
+ end
+
+ context 'when the feature is disabled' do
+ before do
+ stub_feature_flags(customer_relations: false)
+ end
+
+ it 'raises expected error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_errors).to include(a_hash_including('message' => 'Feature disabled'))
+ end
+ end
+
+ context 'replace' do
+ it 'updates the issue with correct contacts' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes, :id))
+ .to match_array([global_id_of(contacts[1]), global_id_of(contacts[2])])
+ end
+ end
+
+ context 'append' do
+ let(:crm_contact_ids) { [global_id_of(contacts[3])] }
+ let(:operation_mode) { Types::MutationOperationModeEnum.enum[:append] }
+
+ it 'updates the issue with correct contacts' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes, :id))
+ .to match_array([global_id_of(contacts[0]), global_id_of(contacts[1]), global_id_of(contacts[3])])
+ end
+ end
+
+ context 'remove' do
+ let(:crm_contact_ids) { [global_id_of(contacts[0])] }
+ let(:operation_mode) { Types::MutationOperationModeEnum.enum[:remove] }
+
+ it 'updates the issue with correct contacts' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes, :id))
+ .to match_array([global_id_of(contacts[1])])
+ end
+ end
+
+ context 'when the contact does not exist' do
+ let(:crm_contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
+
+ it 'returns expected error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_data_at(:issue_set_crm_contacts, :errors))
+ .to match_array(["Issue customer relations contacts #{non_existing_record_id}: #{does_not_exist_or_no_permission}"])
+ end
+ end
+
+ context 'when the contact belongs to a different group' do
+ let(:group2) { create(:group) }
+ let(:contact) { create(:contact, group: group2) }
+ let(:crm_contact_ids) { [global_id_of(contact)] }
+
+ before do
+ group2.add_reporter(user)
+ end
+
+ it 'returns expected error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_data_at(:issue_set_crm_contacts, :errors))
+ .to match_array(["Issue customer relations contacts #{contact.id}: #{does_not_exist_or_no_permission}"])
+ end
+ end
+
+ context 'when attempting to add more than 6' do
+ let(:operation_mode) { Types::MutationOperationModeEnum.enum[:append] }
+ let(:gid) { global_id_of(contacts[0]) }
+ let(:crm_contact_ids) { [gid, gid, gid, gid, gid, gid, gid] }
+
+ it 'returns expected error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_data_at(:issue_set_crm_contacts, :errors))
+ .to match_array(["You can only add up to 6 contacts at one time"])
+ end
+ end
+
+ context 'when trying to remove non-existent contact' do
+ let(:operation_mode) { Types::MutationOperationModeEnum.enum[:remove] }
+ let(:crm_contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
+
+ it 'raises expected error' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(graphql_data_at(:issue_set_crm_contacts, :errors)).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
index 72e47a98373..8e223b6fdaf 100644
--- a/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_due_date_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe 'Setting Due Date of an issue' do
end
it 'returns an error if the user is not allowed to update the issue' do
- error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
post_graphql_mutation(mutation, current_user: create(:user))
expect(graphql_errors).to include(a_hash_including('message' => error))
diff --git a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
index 41997f151a2..cd9d695bd2c 100644
--- a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe 'Setting severity level of an incident' do
context 'when the user is not allowed to update the incident' do
it 'returns an error' do
- error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ error = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
post_graphql_mutation(mutation, current_user: user)
expect(response).to have_gitlab_http_status(:success)
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb
index 2143abd3031..bea2365eaa6 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb
@@ -8,14 +8,14 @@ RSpec.describe 'Setting Draft status of a merge request' do
let(:current_user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
- let(:input) { { wip: true } }
+ let(:input) { { draft: true } }
let(:mutation) do
variables = {
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_set_wip, variables.merge(input),
+ graphql_mutation(:merge_request_set_draft, variables.merge(input),
<<-QL.strip_heredoc
clientMutationId
errors
@@ -28,7 +28,7 @@ RSpec.describe 'Setting Draft status of a merge request' do
end
def mutation_response
- graphql_mutation_response(:merge_request_set_wip)
+ graphql_mutation_response(:merge_request_set_draft)
end
before do
@@ -58,7 +58,7 @@ RSpec.describe 'Setting Draft status of a merge request' do
end
context 'when passing Draft false as input' do
- let(:input) { { wip: false } }
+ let(:input) { { draft: false } }
it 'does not do anything if the merge reqeust was not marked draft' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/merge_requests/update_reviewer_state_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/update_reviewer_state_spec.rb
new file mode 100644
index 00000000000..cf497cb2579
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/merge_requests/update_reviewer_state_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Toggle attention requested for reviewer' do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:merge_request) { create(:merge_request, reviewers: [user]) }
+ let(:project) { merge_request.project }
+ let(:user) { create(:user) }
+ let(:input) { { user_id: global_id_of(user) } }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: merge_request.iid.to_s
+ }
+ graphql_mutation(:merge_request_toggle_attention_requested, variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ QL
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:merge_request_toggle_attention_requested)
+ end
+
+ def mutation_errors
+ mutation_response['errors']
+ end
+
+ before do
+ project.add_developer(current_user)
+ project.add_developer(user)
+ end
+
+ it 'returns an error if the user is not allowed to update the merge request' do
+ post_graphql_mutation(mutation, current_user: create(:user))
+
+ expect(graphql_errors).not_to be_empty
+ end
+
+ describe 'reviewer does not exist' do
+ let(:input) { { user_id: global_id_of(create(:user)) } }
+
+ it 'returns an error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_errors).not_to be_empty
+ end
+ end
+
+ describe 'reviewer exists' do
+ it 'does not return an error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_errors).to be_empty
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/releases/create_spec.rb b/spec/requests/api/graphql/mutations/releases/create_spec.rb
index a4918cd560c..86995c10f10 100644
--- a/spec/requests/api/graphql/mutations/releases/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/releases/create_spec.rb
@@ -342,7 +342,7 @@ RSpec.describe 'Creation of a new release' do
end
context "when the current user doesn't have access to create releases" do
- expected_error_message = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ expected_error_message = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
context 'when the current user is a Reporter' do
let(:current_user) { reporter }
diff --git a/spec/requests/api/graphql/mutations/releases/delete_spec.rb b/spec/requests/api/graphql/mutations/releases/delete_spec.rb
index 40063156609..eb4f0b594ea 100644
--- a/spec/requests/api/graphql/mutations/releases/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/releases/delete_spec.rb
@@ -50,7 +50,7 @@ RSpec.describe 'Deleting a release' do
expect(mutation_response).to be_nil
expect(graphql_errors.count).to eq(1)
- expect(graphql_errors.first['message']).to eq("The resource that you are attempting to access does not exist or you don't have permission to perform this action")
+ expect(graphql_errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
end
end
diff --git a/spec/requests/api/graphql/mutations/releases/update_spec.rb b/spec/requests/api/graphql/mutations/releases/update_spec.rb
index c9a6c3abd57..0fa3d7de299 100644
--- a/spec/requests/api/graphql/mutations/releases/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/releases/update_spec.rb
@@ -218,13 +218,13 @@ RSpec.describe 'Updating an existing release' do
context 'when the project does not exist' do
let(:mutation_arguments) { super().merge(projectPath: 'not/a/real/path') }
- it_behaves_like 'top-level error with message', "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ it_behaves_like 'top-level error with message', Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
end
end
end
context "when the current user doesn't have access to update releases" do
- expected_error_message = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ expected_error_message = Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR
context 'when the current user is a Reporter' do
let(:current_user) { reporter }
diff --git a/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb
new file mode 100644
index 00000000000..929609d4160
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/security/ci_configuration/configure_sast_iac_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'ConfigureSastIac' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :test_repo) }
+
+ let(:variables) { { project_path: project.full_path } }
+ let(:mutation) { graphql_mutation(:configure_sast_iac, variables) }
+ let(:mutation_response) { graphql_mutation_response(:configureSastIac) }
+
+ context 'when authorized' do
+ let_it_be(:user) { project.owner }
+
+ it 'creates a branch with sast iac configured' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['errors']).to be_empty
+ expect(mutation_response['branch']).not_to be_empty
+ expect(mutation_response['successPath']).not_to be_empty
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/namespace_query_spec.rb b/spec/requests/api/graphql/namespace_query_spec.rb
new file mode 100644
index 00000000000..f7ee2bcb55d
--- /dev/null
+++ b/spec/requests/api/graphql/namespace_query_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:other_user) { create(:user) }
+
+ let_it_be(:group_namespace) { create(:group) }
+ let_it_be(:user_namespace) { create(:user_namespace, owner: user) }
+ let_it_be(:project_namespace) { create(:project_namespace, parent: group_namespace) }
+
+ describe '.namespace' do
+ subject { post_graphql(query, current_user: current_user) }
+
+ let(:current_user) { user }
+
+ let(:query) { graphql_query_for(:namespace, { 'fullPath' => target_namespace.full_path }, all_graphql_fields_for('Namespace')) }
+ let(:query_result) { graphql_data['namespace'] }
+
+ shared_examples 'retrieving a namespace' do
+ context 'authorised query' do
+ before do
+ subject
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'fetches the expected data' do
+ expect(query_result).to include(
+ 'fullPath' => target_namespace.full_path,
+ 'name' => target_namespace.name
+ )
+ end
+ end
+
+ context 'unauthorised query' do
+ before do
+ subject
+ end
+
+ context 'anonymous user' do
+ let(:current_user) { nil }
+
+ it 'does not retrieve the record' do
+ expect(query_result).to be_nil
+ end
+ end
+
+ context 'the current user does not have permission' do
+ let(:current_user) { other_user }
+
+ it 'does not retrieve the record' do
+ expect(query_result).to be_nil
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'retrieving a namespace' do
+ let(:target_namespace) { group_namespace }
+
+ before do
+ group_namespace.add_developer(user)
+ end
+ end
+
+ it_behaves_like 'retrieving a namespace' do
+ let(:target_namespace) { user_namespace }
+ end
+
+ context 'does not retrieve project namespace' do
+ let(:target_namespace) { project_namespace }
+
+ before do
+ subject
+ end
+
+ it 'does not retrieve the record' do
+ expect(query_result).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/packages/helm_spec.rb b/spec/requests/api/graphql/packages/helm_spec.rb
new file mode 100644
index 00000000000..397096f70db
--- /dev/null
+++ b/spec/requests/api/graphql/packages/helm_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe 'helm package details' do
+ include GraphqlHelpers
+ include_context 'package details setup'
+
+ let_it_be(:package) { create(:helm_package, project: project) }
+
+ let(:package_files_metadata) {query_graphql_fragment('HelmFileMetadata')}
+
+ let(:query) do
+ graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
+ #{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)}
+ packageFiles {
+ nodes {
+ #{package_files}
+ fileMetadata {
+ #{package_files_metadata}
+ }
+ }
+ }
+ FIELDS
+ end
+
+ subject { post_graphql(query, current_user: user) }
+
+ before do
+ subject
+ end
+
+ it_behaves_like 'a package detail'
+ it_behaves_like 'a package with files'
+
+ it 'has the correct file metadata' do
+ expect(first_file_response_metadata).to include(
+ 'channel' => first_file.helm_file_metadatum.channel
+ )
+ expect(first_file_response_metadata['metadata']).to include(
+ 'name' => first_file.helm_file_metadatum.metadata['name'],
+ 'home' => first_file.helm_file_metadatum.metadata['home'],
+ 'sources' => first_file.helm_file_metadatum.metadata['sources'],
+ 'version' => first_file.helm_file_metadatum.metadata['version'],
+ 'description' => first_file.helm_file_metadatum.metadata['description'],
+ 'keywords' => first_file.helm_file_metadatum.metadata['keywords'],
+ 'maintainers' => first_file.helm_file_metadatum.metadata['maintainers'],
+ 'icon' => first_file.helm_file_metadatum.metadata['icon'],
+ 'apiVersion' => first_file.helm_file_metadatum.metadata['apiVersion'],
+ 'condition' => first_file.helm_file_metadatum.metadata['condition'],
+ 'tags' => first_file.helm_file_metadatum.metadata['tags'],
+ 'appVersion' => first_file.helm_file_metadatum.metadata['appVersion'],
+ 'deprecated' => first_file.helm_file_metadatum.metadata['deprecated'],
+ 'annotations' => first_file.helm_file_metadatum.metadata['annotations'],
+ 'kubeVersion' => first_file.helm_file_metadatum.metadata['kubeVersion'],
+ 'dependencies' => first_file.helm_file_metadatum.metadata['dependencies'],
+ 'type' => first_file.helm_file_metadatum.metadata['type']
+ )
+ end
+end
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 1c6d6ce4707..b3e91afb5b3 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -429,11 +429,11 @@ RSpec.describe 'getting an issue list for a project' do
end
it 'avoids N+1 queries' do
- create(:contact, group_id: group.id, issues: [issue_a])
+ create(:issue_customer_relations_contact, :for_issue, issue: issue_a)
control = ActiveRecord::QueryRecorder.new(skip_cached: false) { clean_state_query }
- create(:contact, group_id: group.id, issues: [issue_a])
+ create(:issue_customer_relations_contact, :for_issue, issue: issue_a)
expect { clean_state_query }.not_to exceed_all_query_limit(control)
end
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index 438ea9bb4c1..353bf0356f6 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -347,7 +347,7 @@ RSpec.describe 'getting merge request information nested in a project' do
expect(interaction_data).to contain_exactly a_hash_including(
'canMerge' => false,
'canUpdate' => can_update,
- 'reviewState' => unreviewed,
+ 'reviewState' => attention_requested,
'reviewed' => false,
'approved' => false
)
@@ -380,8 +380,8 @@ RSpec.describe 'getting merge request information nested in a project' do
describe 'scalability' do
let_it_be(:other_users) { create_list(:user, 3) }
- let(:unreviewed) do
- { 'reviewState' => 'UNREVIEWED' }
+ let(:attention_requested) do
+ { 'reviewState' => 'ATTENTION_REQUESTED' }
end
let(:reviewed) do
@@ -413,9 +413,9 @@ RSpec.describe 'getting merge request information nested in a project' do
expect { post_graphql(query) }.not_to exceed_query_limit(baseline)
expect(interaction_data).to contain_exactly(
- include(unreviewed),
- include(unreviewed),
- include(unreviewed),
+ include(attention_requested),
+ include(attention_requested),
+ include(attention_requested),
include(reviewed)
)
end
@@ -444,7 +444,7 @@ RSpec.describe 'getting merge request information nested in a project' do
it_behaves_like 'when requesting information about MR interactions' do
let(:field) { :reviewers }
- let(:unreviewed) { 'UNREVIEWED' }
+ let(:attention_requested) { 'ATTENTION_REQUESTED' }
let(:can_update) { false }
def assign_user(user)
@@ -454,7 +454,7 @@ RSpec.describe 'getting merge request information nested in a project' do
it_behaves_like 'when requesting information about MR interactions' do
let(:field) { :assignees }
- let(:unreviewed) { nil }
+ let(:attention_requested) { nil }
let(:can_update) { true } # assignees can update MRs
def assign_user(user)
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index 7f24d051457..77abac4ef04 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -228,6 +228,189 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
end
end
+ shared_examples 'restricted access to release fields' do
+ describe 'scalar fields' do
+ let(:path) { path_prefix }
+
+ let(:release_fields) do
+ %{
+ tagName
+ tagPath
+ description
+ descriptionHtml
+ name
+ createdAt
+ releasedAt
+ upcomingRelease
+ }
+ end
+
+ before do
+ post_query
+ end
+
+ it 'finds all release data' do
+ expect(data).to eq({
+ 'tagName' => release.tag,
+ 'tagPath' => nil,
+ 'description' => release.description,
+ 'descriptionHtml' => release.description_html,
+ 'name' => release.name,
+ 'createdAt' => release.created_at.iso8601,
+ 'releasedAt' => release.released_at.iso8601,
+ 'upcomingRelease' => false
+ })
+ end
+ end
+
+ describe 'milestones' do
+ let(:path) { path_prefix + %w[milestones nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:milestones, nil, 'nodes { id title }')
+ end
+
+ it 'finds milestones associated to a release' do
+ post_query
+
+ expected = release.milestones.order_by_dates_and_title.map do |milestone|
+ { 'id' => global_id_of(milestone), 'title' => milestone.title }
+ end
+
+ expect(data).to eq(expected)
+ end
+ end
+
+ describe 'author' do
+ let(:path) { path_prefix + %w[author] }
+
+ let(:release_fields) do
+ query_graphql_field(:author, nil, 'id username')
+ end
+
+ it 'finds the author of the release' do
+ post_query
+
+ expect(data).to eq(
+ 'id' => global_id_of(release.author),
+ 'username' => release.author.username
+ )
+ end
+ end
+
+ describe 'commit' do
+ let(:path) { path_prefix + %w[commit] }
+
+ let(:release_fields) do
+ query_graphql_field(:commit, nil, 'sha')
+ end
+
+ it 'restricts commit associated with the release' do
+ post_query
+
+ expect(data).to eq(nil)
+ end
+ end
+
+ describe 'assets' do
+ describe 'count' do
+ let(:path) { path_prefix + %w[assets] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil, 'count')
+ end
+
+ it 'returns non source release links count' do
+ post_query
+
+ expect(data).to eq('count' => release.assets_count(except: [:sources]))
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[assets links nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:links, nil, 'nodes { id name url external, directAssetUrl }'))
+ end
+
+ it 'finds all non source external release links' do
+ post_query
+
+ expected = release.links.map do |link|
+ {
+ 'id' => global_id_of(link),
+ 'name' => link.name,
+ 'url' => link.url,
+ 'external' => true,
+ 'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << "/downloads#{link.filepath}" : link.url
+ }
+ end
+
+ expect(data).to match_array(expected)
+ end
+ end
+
+ describe 'sources' do
+ let(:path) { path_prefix + %w[assets sources nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:sources, nil, 'nodes { format url }'))
+ end
+
+ it 'restricts release sources' do
+ post_query
+
+ expect(data).to match_array([])
+ end
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[links] }
+
+ let(:release_fields) do
+ query_graphql_field(:links, nil, %{
+ selfUrl
+ openedMergeRequestsUrl
+ mergedMergeRequestsUrl
+ closedMergeRequestsUrl
+ openedIssuesUrl
+ closedIssuesUrl
+ })
+ end
+
+ it 'finds only selfUrl' do
+ post_query
+
+ expect(data).to eq(
+ 'selfUrl' => project_release_url(project, release),
+ 'openedMergeRequestsUrl' => nil,
+ 'mergedMergeRequestsUrl' => nil,
+ 'closedMergeRequestsUrl' => nil,
+ 'openedIssuesUrl' => nil,
+ 'closedIssuesUrl' => nil
+ )
+ end
+ end
+
+ describe 'evidences' do
+ let(:path) { path_prefix + %w[evidences] }
+
+ let(:release_fields) do
+ query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
+ end
+
+ it 'restricts all evidence fields' do
+ post_query
+
+ expect(data).to eq('nodes' => [])
+ end
+ end
+ end
+
shared_examples 'no access to the release field' do
describe 'repository-related fields' do
let(:path) { path_prefix }
@@ -302,7 +485,8 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
context 'when the user has Guest permissions' do
let(:current_user) { guest }
- it_behaves_like 'no access to the release field'
+ it_behaves_like 'restricted access to release fields'
+ it_behaves_like 'no access to editUrl'
end
context 'when the user has Reporter permissions' do
diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb
index 2816ce90a6b..c28a6fa7666 100644
--- a/spec/requests/api/graphql/project/releases_spec.rb
+++ b/spec/requests/api/graphql/project/releases_spec.rb
@@ -129,10 +129,12 @@ RSpec.describe 'Query.project(fullPath).releases()' do
end
it 'does not return data for fields that expose repository information' do
+ tag_name = release.tag
+ release_name = release.name
expect(data).to eq(
- 'tagName' => nil,
+ 'tagName' => tag_name,
'tagPath' => nil,
- 'name' => "Release-#{release.id}",
+ 'name' => release_name,
'commit' => nil,
'assets' => {
'count' => release.assets_count(except: [:sources]),
@@ -143,7 +145,14 @@ RSpec.describe 'Query.project(fullPath).releases()' do
'evidences' => {
'nodes' => []
},
- 'links' => nil
+ 'links' => {
+ 'closedIssuesUrl' => nil,
+ 'closedMergeRequestsUrl' => nil,
+ 'mergedMergeRequestsUrl' => nil,
+ 'openedIssuesUrl' => nil,
+ 'openedMergeRequestsUrl' => nil,
+ 'selfUrl' => project_release_url(project, release)
+ }
)
end
end
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index 7d182a3414b..b8f7af29a9f 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -12,21 +12,33 @@ RSpec.describe 'GraphQL' do
describe 'logging' do
shared_examples 'logging a graphql query' do
- let(:expected_params) do
+ let(:expected_execute_query_log) do
{
- query_string: query,
- variables: variables.to_s,
- duration_s: anything,
+ "correlation_id" => kind_of(String),
+ "meta.caller_id" => "graphql:anonymous",
+ "meta.client_id" => kind_of(String),
+ "meta.feature_category" => "not_owned",
+ "meta.remote_ip" => kind_of(String),
+ "query_analysis.duration_s" => kind_of(Numeric),
+ "query_analysis.depth" => 1,
+ "query_analysis.complexity" => 1,
+ "query_analysis.used_fields" => ['Query.echo'],
+ "query_analysis.used_deprecated_fields" => [],
+ # query_fingerprint starts with operation name
+ query_fingerprint: %r{^anonymous\/},
+ duration_s: kind_of(Numeric),
+ trace_type: 'execute_query',
operation_name: nil,
- depth: 1,
- complexity: 1,
- used_fields: ['Query.echo'],
- used_deprecated_fields: []
+ # operation_fingerprint starts with operation name
+ operation_fingerprint: %r{^anonymous\/},
+ is_mutation: false,
+ variables: variables.to_s,
+ query_string: query
}
end
it 'logs a query with the expected params' do
- expect(Gitlab::GraphqlLogger).to receive(:info).with(expected_params).once
+ expect(Gitlab::GraphqlLogger).to receive(:info).with(expected_execute_query_log).once
post_graphql(query, variables: variables)
end
diff --git a/spec/requests/api/group_debian_distributions_spec.rb b/spec/requests/api/group_debian_distributions_spec.rb
index ec1912b72bf..21c5f2f09a0 100644
--- a/spec/requests/api/group_debian_distributions_spec.rb
+++ b/spec/requests/api/group_debian_distributions_spec.rb
@@ -11,19 +11,25 @@ RSpec.describe API::GroupDebianDistributions do
let(:url) { "/groups/#{container.id}/-/debian_distributions" }
let(:api_params) { { 'codename': 'my-codename' } }
- it_behaves_like 'Debian repository write endpoint', 'POST distribution request', :created, /^{.*"codename":"my-codename",.*"components":\["main"\],.*"architectures":\["all","amd64"\]/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions write endpoint', 'POST', :created, /^{.*"codename":"my-codename",.*"components":\["main"\],.*"architectures":\["all","amd64"\]/
end
describe 'GET groups/:id/-/debian_distributions' do
let(:url) { "/groups/#{container.id}/-/debian_distributions" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^\[{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^\[{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/
end
describe 'GET groups/:id/-/debian_distributions/:codename' do
let(:url) { "/groups/#{container.id}/-/debian_distributions/#{distribution.codename}" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^{.*"codename":"existing-codename",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/
+ end
+
+ describe 'GET groups/:id/-/debian_distributions/:codename/key.asc' do
+ let(:url) { "/groups/#{container.id}/-/debian_distributions/#{distribution.codename}/key.asc" }
+
+ it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^-----BEGIN PGP PUBLIC KEY BLOCK-----/
end
describe 'PUT groups/:id/-/debian_distributions/:codename' do
@@ -31,14 +37,14 @@ RSpec.describe API::GroupDebianDistributions do
let(:url) { "/groups/#{container.id}/-/debian_distributions/#{distribution.codename}" }
let(:api_params) { { suite: 'my-suite' } }
- it_behaves_like 'Debian repository write endpoint', 'PUT distribution request', :success, /^{.*"codename":"existing-codename",.*"suite":"my-suite",/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions write endpoint', 'PUT', :success, /^{.*"codename":"existing-codename",.*"suite":"my-suite",/
end
describe 'DELETE groups/:id/-/debian_distributions/:codename' do
let(:method) { :delete }
let(:url) { "/groups/#{container.id}/-/debian_distributions/#{distribution.codename}" }
- it_behaves_like 'Debian repository maintainer write endpoint', 'DELETE distribution request', :success, /^{"message":"202 Accepted"}$/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions maintainer write endpoint', 'DELETE', :success, /^{"message":"202 Accepted"}$/
end
end
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index cee727ae6fe..75f5a974d22 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -319,12 +319,15 @@ RSpec.describe API::Groups do
it "includes statistics if requested" do
attributes = {
- storage_size: 2392,
+ storage_size: 4093,
repository_size: 123,
wiki_size: 456,
lfs_objects_size: 234,
build_artifacts_size: 345,
- snippets_size: 1234
+ pipeline_artifacts_size: 456,
+ packages_size: 567,
+ snippets_size: 1234,
+ uploads_size: 678
}.stringify_keys
exposed_attributes = attributes.dup
exposed_attributes['job_artifacts_size'] = exposed_attributes.delete('build_artifacts_size')
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index aeca4e435f4..0a71eb43f81 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -948,7 +948,7 @@ RSpec.describe API::Internal::Base do
context 'user does not exist' do
it do
- pull(OpenStruct.new(id: 0), project)
+ pull(double('key', id: 0), project)
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response["status"]).to be_falsey
diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb
index b23ba0021e0..cba4256adc5 100644
--- a/spec/requests/api/invitations_spec.rb
+++ b/spec/requests/api/invitations_spec.rb
@@ -166,6 +166,38 @@ RSpec.describe API::Invitations do
end
end
+ context 'with tasks_to_be_done and tasks_project_id in the params' do
+ before do
+ stub_experiments(invite_members_for_task: true)
+ end
+
+ let(:project_id) { source_type == 'project' ? source.id : create(:project, namespace: source).id }
+
+ context 'when there is 1 invitation' do
+ it 'creates a member_task with the tasks_to_be_done and the project' do
+ post invitations_url(source, maintainer),
+ params: { email: email, access_level: Member::DEVELOPER, tasks_to_be_done: %w(code ci), tasks_project_id: project_id }
+
+ member = source.members.find_by(invite_email: email)
+ expect(member.tasks_to_be_done).to match_array([:code, :ci])
+ expect(member.member_task.project_id).to eq(project_id)
+ end
+ end
+
+ context 'when there are multiple invitations' do
+ it 'creates a member_task with the tasks_to_be_done and the project' do
+ post invitations_url(source, maintainer),
+ params: { email: [email, email2].join(','), access_level: Member::DEVELOPER, tasks_to_be_done: %w(code ci), tasks_project_id: project_id }
+
+ members = source.members.where(invite_email: [email, email2])
+ members.each do |member|
+ expect(member.tasks_to_be_done).to match_array([:code, :ci])
+ expect(member.member_task.project_id).to eq(project_id)
+ end
+ end
+ end
+ end
+
context 'with invite_source considerations', :snowplow do
let(:params) { { email: email, access_level: Member::DEVELOPER } }
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index d7f22b9d619..ac30da99afe 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -102,6 +102,13 @@ RSpec.describe API::Lint do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('merged_yaml')
end
+
+ it 'outputs jobs' do
+ post api('/ci/lint', api_user), params: { content: yaml_content, include_jobs: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to have_key('jobs')
+ end
end
context 'with valid .gitlab-ci.yaml with warnings' do
@@ -136,6 +143,13 @@ RSpec.describe API::Lint do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('merged_yaml')
end
+
+ it 'outputs jobs' do
+ post api('/ci/lint', api_user), params: { content: yaml_content, include_jobs: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to have_key('jobs')
+ end
end
context 'with invalid configuration' do
@@ -156,6 +170,13 @@ RSpec.describe API::Lint do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to have_key('merged_yaml')
end
+
+ it 'outputs jobs' do
+ post api('/ci/lint', api_user), params: { content: yaml_content, include_jobs: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to have_key('jobs')
+ end
end
end
@@ -171,10 +192,11 @@ RSpec.describe API::Lint do
end
describe 'GET /projects/:id/ci/lint' do
- subject(:ci_lint) { get api("/projects/#{project.id}/ci/lint", api_user), params: { dry_run: dry_run } }
+ subject(:ci_lint) { get api("/projects/#{project.id}/ci/lint", api_user), params: { dry_run: dry_run, include_jobs: include_jobs } }
let(:project) { create(:project, :repository) }
let(:dry_run) { nil }
+ let(:include_jobs) { nil }
RSpec.shared_examples 'valid config with warnings' do
it 'passes validation with warnings' do
@@ -359,6 +381,30 @@ RSpec.describe API::Lint do
it_behaves_like 'valid config without warnings'
end
+ context 'when running with include jobs' do
+ let(:include_jobs) { true }
+
+ it_behaves_like 'valid config without warnings'
+
+ it 'returns jobs key' do
+ ci_lint
+
+ expect(json_response).to have_key('jobs')
+ end
+ end
+
+ context 'when running without include jobs' do
+ let(:include_jobs) { false }
+
+ it_behaves_like 'valid config without warnings'
+
+ it 'does not return jobs key' do
+ ci_lint
+
+ expect(json_response).not_to have_key('jobs')
+ end
+ end
+
context 'With warnings' do
let(:yaml_content) { { job: { script: 'ls', rules: [{ when: 'always' }] } }.to_yaml }
@@ -386,15 +432,40 @@ RSpec.describe API::Lint do
it_behaves_like 'invalid config'
end
+
+ context 'when running with include jobs' do
+ let(:include_jobs) { true }
+
+ it_behaves_like 'invalid config'
+
+ it 'returns jobs key' do
+ ci_lint
+
+ expect(json_response).to have_key('jobs')
+ end
+ end
+
+ context 'when running without include jobs' do
+ let(:include_jobs) { false }
+
+ it_behaves_like 'invalid config'
+
+ it 'does not return jobs key' do
+ ci_lint
+
+ expect(json_response).not_to have_key('jobs')
+ end
+ end
end
end
end
describe 'POST /projects/:id/ci/lint' do
- subject(:ci_lint) { post api("/projects/#{project.id}/ci/lint", api_user), params: { dry_run: dry_run, content: yaml_content } }
+ subject(:ci_lint) { post api("/projects/#{project.id}/ci/lint", api_user), params: { dry_run: dry_run, content: yaml_content, include_jobs: include_jobs } }
let(:project) { create(:project, :repository) }
let(:dry_run) { nil }
+ let(:include_jobs) { nil }
let_it_be(:api_user) { create(:user) }
@@ -562,6 +633,30 @@ RSpec.describe API::Lint do
it_behaves_like 'valid project config'
end
+
+ context 'when running with include jobs param' do
+ let(:include_jobs) { true }
+
+ it_behaves_like 'valid project config'
+
+ it 'contains jobs key' do
+ ci_lint
+
+ expect(json_response).to have_key('jobs')
+ end
+ end
+
+ context 'when running without include jobs param' do
+ let(:include_jobs) { false }
+
+ it_behaves_like 'valid project config'
+
+ it 'does not contain jobs key' do
+ ci_lint
+
+ expect(json_response).not_to have_key('jobs')
+ end
+ end
end
context 'with invalid .gitlab-ci.yml content' do
@@ -580,6 +675,30 @@ RSpec.describe API::Lint do
it_behaves_like 'invalid project config'
end
+
+ context 'when running with include jobs set to false' do
+ let(:include_jobs) { false }
+
+ it_behaves_like 'invalid project config'
+
+ it 'does not contain jobs key' do
+ ci_lint
+
+ expect(json_response).not_to have_key('jobs')
+ end
+ end
+
+ context 'when running with param include jobs' do
+ let(:include_jobs) { true }
+
+ it_behaves_like 'invalid project config'
+
+ it 'contains jobs key' do
+ ci_lint
+
+ expect(json_response).to have_key('jobs')
+ end
+ end
end
end
end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index a1daf86de31..7f4345faabb 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -81,14 +81,22 @@ RSpec.describe API::Members do
expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id]
end
- it 'finds members with query string' do
- get api(members_url, developer), params: { query: maintainer.username }
+ context 'with cross db check disabled' do
+ around do |example|
+ allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/343305') do
+ example.run
+ end
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.count).to eq(1)
- expect(json_response.first['username']).to eq(maintainer.username)
+ it 'finds members with query string' do
+ get api(members_url, developer), params: { query: maintainer.username }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['username']).to eq(maintainer.username)
+ end
end
it 'finds members with the given user_ids' do
@@ -406,6 +414,38 @@ RSpec.describe API::Members do
end
end
+ context 'with tasks_to_be_done and tasks_project_id in the params' do
+ before do
+ stub_experiments(invite_members_for_task: true)
+ end
+
+ let(:project_id) { source_type == 'project' ? source.id : create(:project, namespace: source).id }
+
+ context 'when there is 1 user to add' do
+ it 'creates a member_task with the correct attributes' do
+ post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
+ params: { user_id: stranger.id, access_level: Member::DEVELOPER, tasks_to_be_done: %w(code ci), tasks_project_id: project_id }
+
+ member = source.members.find_by(user_id: stranger.id)
+ expect(member.tasks_to_be_done).to match_array([:code, :ci])
+ expect(member.member_task.project_id).to eq(project_id)
+ end
+ end
+
+ context 'when there are multiple users to add' do
+ it 'creates a member_task with the correct attributes' do
+ post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
+ params: { user_id: [developer.id, stranger.id].join(','), access_level: Member::DEVELOPER, tasks_to_be_done: %w(code ci), tasks_project_id: project_id }
+
+ members = source.members.where(user_id: [developer.id, stranger.id])
+ members.each do |member|
+ expect(member.tasks_to_be_done).to match_array([:code, :ci])
+ expect(member.member_task.project_id).to eq(project_id)
+ end
+ end
+ end
+ end
+
it "returns 409 if member already exists" do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
params: { user_id: maintainer.id, access_level: Member::MAINTAINER }
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index bdbc73a59d8..7c147419354 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -3278,6 +3278,8 @@ RSpec.describe API::MergeRequests do
context 'when skip_ci parameter is set' do
it 'enqueues a rebase of the merge request with skip_ci flag set' do
+ allow(RebaseWorker).to receive(:with_status).and_return(RebaseWorker)
+
expect(RebaseWorker).to receive(:perform_async).with(merge_request.id, user.id, true).and_call_original
Sidekiq::Testing.fake! do
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 222d8992d1b..01dbf523071 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -3,10 +3,12 @@
require 'spec_helper'
RSpec.describe API::Namespaces do
- let(:admin) { create(:admin) }
- let(:user) { create(:user) }
- let!(:group1) { create(:group, name: 'group.one') }
- let!(:group2) { create(:group, :nested) }
+ let_it_be(:admin) { create(:admin) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group1) { create(:group, name: 'group.one') }
+ let_it_be(:group2) { create(:group, :nested) }
+ let_it_be(:project) { create(:project, namespace: group2, name: group2.name, path: group2.path) }
+ let_it_be(:project_namespace) { project.project_namespace }
describe "GET /namespaces" do
context "when unauthenticated" do
@@ -26,7 +28,7 @@ RSpec.describe API::Namespaces do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(group_kind_json_response.keys).to include('id', 'kind', 'name', 'path', 'full_path',
- 'parent_id', 'members_count_with_descendants')
+ 'parent_id', 'members_count_with_descendants')
expect(user_kind_json_response.keys).to include('id', 'kind', 'name', 'path', 'full_path', 'parent_id')
end
@@ -37,7 +39,8 @@ RSpec.describe API::Namespaces do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(Namespace.count)
+ # project namespace is excluded
+ expect(json_response.length).to eq(Namespace.count - 1)
end
it "admin: returns an array of matched namespaces" do
@@ -61,7 +64,7 @@ RSpec.describe API::Namespaces do
owned_group_response = json_response.find { |resource| resource['id'] == group1.id }
expect(owned_group_response.keys).to include('id', 'kind', 'name', 'path', 'full_path',
- 'parent_id', 'members_count_with_descendants')
+ 'parent_id', 'members_count_with_descendants')
end
it "returns correct attributes when user cannot admin group" do
@@ -109,7 +112,8 @@ RSpec.describe API::Namespaces do
describe 'GET /namespaces/:id' do
let(:owned_group) { group1 }
- let(:user2) { create(:user) }
+
+ let_it_be(:user2) { create(:user) }
shared_examples 'can access namespace' do
it 'returns namespace details' do
@@ -144,6 +148,16 @@ RSpec.describe API::Namespaces do
it_behaves_like 'can access namespace'
end
+
+ context 'when requesting project_namespace' do
+ let(:namespace_id) { project_namespace.id }
+
+ it 'returns not-found' do
+ get api("/namespaces/#{namespace_id}", request_actor)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
context 'when requested by path' do
@@ -159,6 +173,16 @@ RSpec.describe API::Namespaces do
it_behaves_like 'can access namespace'
end
+
+ context 'when requesting project_namespace' do
+ let(:namespace_id) { project_namespace.full_path }
+
+ it 'returns not-found' do
+ get api("/namespaces/#{namespace_id}", request_actor)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
end
@@ -177,6 +201,12 @@ RSpec.describe API::Namespaces do
expect(response).to have_gitlab_http_status(:unauthorized)
end
+
+ it 'returns authentication error' do
+ get api("/namespaces/#{project_namespace.id}")
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
end
context 'when authenticated as regular user' do
@@ -231,10 +261,10 @@ RSpec.describe API::Namespaces do
end
describe 'GET /namespaces/:namespace/exists' do
- let!(:namespace1) { create(:group, name: 'Namespace 1', path: 'namespace-1') }
- let!(:namespace2) { create(:group, name: 'Namespace 2', path: 'namespace-2') }
- let!(:namespace1sub) { create(:group, name: 'Sub Namespace 1', path: 'sub-namespace-1', parent: namespace1) }
- let!(:namespace2sub) { create(:group, name: 'Sub Namespace 2', path: 'sub-namespace-2', parent: namespace2) }
+ let_it_be(:namespace1) { create(:group, name: 'Namespace 1', path: 'namespace-1') }
+ let_it_be(:namespace2) { create(:group, name: 'Namespace 2', path: 'namespace-2') }
+ let_it_be(:namespace1sub) { create(:group, name: 'Sub Namespace 1', path: 'sub-namespace-1', parent: namespace1) }
+ let_it_be(:namespace2sub) { create(:group, name: 'Sub Namespace 2', path: 'sub-namespace-2', parent: namespace2) }
context 'when unauthenticated' do
it 'returns authentication error' do
@@ -242,6 +272,16 @@ RSpec.describe API::Namespaces do
expect(response).to have_gitlab_http_status(:unauthorized)
end
+
+ context 'when requesting project_namespace' do
+ let(:namespace_id) { project_namespace.id }
+
+ it 'returns authentication error' do
+ get api("/namespaces/#{project_namespace.path}/exists"), params: { parent_id: group2.id }
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
end
context 'when authenticated' do
@@ -300,6 +340,18 @@ RSpec.describe API::Namespaces do
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(expected_json)
end
+
+ context 'when requesting project_namespace' do
+ let(:namespace_id) { project_namespace.id }
+
+ it 'returns JSON indicating the namespace does not exist without a suggestion' do
+ get api("/namespaces/#{project_namespace.path}/exists", user), params: { parent_id: group2.id }
+
+ expected_json = { exists: false, suggests: [] }.to_json
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(expected_json)
+ end
+ end
end
end
end
diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb
index 0d04c2cad5b..7c3f1890095 100644
--- a/spec/requests/api/npm_project_packages_spec.rb
+++ b/spec/requests/api/npm_project_packages_spec.rb
@@ -180,6 +180,7 @@ RSpec.describe API::NpmProjectPackages do
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and change { Packages::Tag.count }.by(1)
+ .and change { Packages::Npm::Metadatum.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
end
@@ -317,6 +318,25 @@ RSpec.describe API::NpmProjectPackages do
end
end
end
+
+ context 'with a too large metadata structure' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) do
+ upload_params(package_name: package_name, package_version: '1.2.3').tap do |h|
+ h['versions']['1.2.3']['test'] = 'test' * 10000
+ end
+ end
+
+ it_behaves_like 'not a package tracking event'
+
+ it 'returns an error' do
+ expect { upload_package_with_token }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.body).to include('Validation failed: Package json structure is too large')
+ end
+ end
end
def upload_package(package_name, params = {})
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index dd00d413664..01d2fb18f00 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -137,6 +137,7 @@ project_setting:
unexposed_attributes:
- created_at
- has_confluence
+ - has_shimo
- has_vulnerabilities
- prevent_merge_without_jira_issue
- warn_about_potentially_unwanted_characters
diff --git a/spec/requests/api/project_debian_distributions_spec.rb b/spec/requests/api/project_debian_distributions_spec.rb
index de7362758f7..2b993f24046 100644
--- a/spec/requests/api/project_debian_distributions_spec.rb
+++ b/spec/requests/api/project_debian_distributions_spec.rb
@@ -11,25 +11,31 @@ RSpec.describe API::ProjectDebianDistributions do
let(:url) { "/projects/#{container.id}/debian_distributions" }
let(:api_params) { { 'codename': 'my-codename' } }
- it_behaves_like 'Debian repository write endpoint', 'POST distribution request', :created, /^{.*"codename":"my-codename",.*"components":\["main"\],.*"architectures":\["all","amd64"\]/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions write endpoint', 'POST', :created, /^{.*"codename":"my-codename",.*"components":\["main"\],.*"architectures":\["all","amd64"\]/
context 'with invalid parameters' do
let(:api_params) { { codename: distribution.codename } }
- it_behaves_like 'Debian repository write endpoint', 'GET request', :bad_request, /^{"message":{"codename":\["has already been taken"\]}}$/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions write endpoint', 'GET', :bad_request, /^{"message":{"codename":\["has already been taken"\]}}$/
end
end
describe 'GET projects/:id/debian_distributions' do
let(:url) { "/projects/#{container.id}/debian_distributions" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^\[{.*"codename":"existing-codename\",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^\[{.*"codename":"existing-codename\",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/
end
describe 'GET projects/:id/debian_distributions/:codename' do
let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}" }
- it_behaves_like 'Debian repository read endpoint', 'GET request', :success, /^{.*"codename":"existing-codename\",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^{.*"codename":"existing-codename\",.*"components":\["existing-component"\],.*"architectures":\["all","existing-arch"\]/
+ end
+
+ describe 'GET projects/:id/debian_distributions/:codename/key.asc' do
+ let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}/key.asc" }
+
+ it_behaves_like 'Debian distributions read endpoint', 'GET', :success, /^-----BEGIN PGP PUBLIC KEY BLOCK-----/
end
describe 'PUT projects/:id/debian_distributions/:codename' do
@@ -37,12 +43,12 @@ RSpec.describe API::ProjectDebianDistributions do
let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}" }
let(:api_params) { { suite: 'my-suite' } }
- it_behaves_like 'Debian repository write endpoint', 'PUT distribution request', :success, /^{.*"codename":"existing-codename",.*"suite":"my-suite",/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions write endpoint', 'PUT', :success, /^{.*"codename":"existing-codename",.*"suite":"my-suite",/
context 'with invalid parameters' do
let(:api_params) { { suite: distribution.codename } }
- it_behaves_like 'Debian repository write endpoint', 'GET request', :bad_request, /^{"message":{"suite":\["has already been taken as Codename"\]}}$/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions write endpoint', 'GET', :bad_request, /^{"message":{"suite":\["has already been taken as Codename"\]}}$/
end
end
@@ -50,7 +56,7 @@ RSpec.describe API::ProjectDebianDistributions do
let(:method) { :delete }
let(:url) { "/projects/#{container.id}/debian_distributions/#{distribution.codename}" }
- it_behaves_like 'Debian repository maintainer write endpoint', 'DELETE distribution request', :success, /^{\"message\":\"202 Accepted\"}$/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions maintainer write endpoint', 'DELETE', :success, /^{\"message\":\"202 Accepted\"}$/
context 'when destroy fails' do
before do
@@ -59,7 +65,7 @@ RSpec.describe API::ProjectDebianDistributions do
end
end
- it_behaves_like 'Debian repository maintainer write endpoint', 'GET request', :bad_request, /^{"message":"Failed to delete distribution"}$/, authenticate_non_public: false
+ it_behaves_like 'Debian distributions maintainer write endpoint', 'GET', :bad_request, /^{"message":"Failed to delete distribution"}$/
end
end
end
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 0c9e125cc90..097d374640c 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -47,7 +47,7 @@ RSpec.describe API::ProjectImport do
it 'executes a limited number of queries' do
control_count = ActiveRecord::QueryRecorder.new { subject }.count
- expect(control_count).to be <= 100
+ expect(control_count).to be <= 101
end
it 'schedules an import using a namespace' do
diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb
index f23e374407b..33c86d56ed4 100644
--- a/spec/requests/api/project_snapshots_spec.rb
+++ b/spec/requests/api/project_snapshots_spec.rb
@@ -29,6 +29,7 @@ RSpec.describe API::ProjectSnapshots do
repository: repository.gitaly_repository
).to_json
)
+ expect(response.parsed_body).to be_empty
end
it 'returns authentication error as project owner' do
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 8cd1f15a88d..512cbf7c321 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -400,6 +400,7 @@ RSpec.describe API::ProjectSnippets do
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq 'text/plain'
+ expect(response.parsed_body).to be_empty
end
it 'returns 404 for invalid snippet id' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index dd6afa869e0..4f84e6f2562 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -48,6 +48,7 @@ end
RSpec.describe API::Projects do
include ProjectForksHelper
+ include StubRequests
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
@@ -358,7 +359,7 @@ RSpec.describe API::Projects do
statistics = json_response.find { |p| p['id'] == project.id }['statistics']
expect(statistics).to be_present
- expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size', 'packages_size')
+ expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'pipeline_artifacts_size', 'snippets_size', 'packages_size', 'uploads_size')
end
it "does not include license by default" do
@@ -1159,6 +1160,34 @@ RSpec.describe API::Projects do
expect(response).to have_gitlab_http_status(:forbidden)
end
+ it 'disallows creating a project with an import_url that is not reachable', :aggregate_failures do
+ url = 'http://example.com'
+ endpoint_url = "#{url}/info/refs?service=git-upload-pack"
+ stub_full_request(endpoint_url, method: :get).to_return({ status: 301, body: '', headers: nil })
+ project_params = { import_url: url, path: 'path-project-Foo', name: 'Foo Project' }
+
+ expect { post api('/projects', user), params: project_params }.not_to change { Project.count }
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq("#{url} is not a valid HTTP Git repository")
+ end
+
+ it 'creates a project with an import_url that is valid', :aggregate_failures do
+ url = 'http://example.com'
+ endpoint_url = "#{url}/info/refs?service=git-upload-pack"
+ git_response = {
+ status: 200,
+ body: '001e# service=git-upload-pack',
+ headers: { 'Content-Type': 'application/x-git-upload-pack-advertisement' }
+ }
+ stub_full_request(endpoint_url, method: :get).to_return(git_response)
+ project_params = { import_url: url, path: 'path-project-Foo', name: 'Foo Project' }
+
+ expect { post api('/projects', user), params: project_params }.to change { Project.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+
it 'sets a project as public' do
project = attributes_for(:project, visibility: 'public')
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index 90b03a480a8..cb9b6a072b1 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -42,6 +42,14 @@ RSpec.describe API::Releases do
expect(response).to have_gitlab_http_status(:ok)
end
+ it 'returns 200 HTTP status when using JOB-TOKEN auth' do
+ job = create(:ci_build, :running, project: project, user: maintainer)
+
+ get api("/projects/#{project.id}/releases"), params: { job_token: job.token }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
it 'returns releases ordered by released_at' do
get api("/projects/#{project.id}/releases", maintainer)
@@ -316,6 +324,14 @@ RSpec.describe API::Releases do
expect(response).to have_gitlab_http_status(:ok)
end
+ it 'returns 200 HTTP status when using JOB-TOKEN auth' do
+ job = create(:ci_build, :running, project: project, user: maintainer)
+
+ get api("/projects/#{project.id}/releases/v0.1"), params: { job_token: job.token }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
it 'returns a release entry' do
get api("/projects/#{project.id}/releases/v0.1", maintainer)
@@ -1008,6 +1024,14 @@ RSpec.describe API::Releases do
expect(response).to have_gitlab_http_status(:ok)
end
+ it 'accepts the request when using JOB-TOKEN auth' do
+ job = create(:ci_build, :running, project: project, user: maintainer)
+
+ put api("/projects/#{project.id}/releases/v0.1"), params: params.merge(job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
it 'updates the description' do
put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params
@@ -1220,6 +1244,14 @@ RSpec.describe API::Releases do
expect(response).to have_gitlab_http_status(:ok)
end
+ it 'accepts the request when using JOB-TOKEN auth' do
+ job = create(:ci_build, :running, project: project, user: maintainer)
+
+ delete api("/projects/#{project.id}/releases/v0.1"), params: { job_token: job.token }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
it 'destroys the release' do
expect do
delete api("/projects/#{project.id}/releases/v0.1", maintainer)
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index f05f125c974..f3146480be2 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -197,6 +197,7 @@ RSpec.describe API::Repositories do
expect(response).to have_gitlab_http_status(:ok)
expect(headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
+ expect(response.parsed_body).to be_empty
end
it 'sets inline content disposition by default' do
@@ -274,6 +275,7 @@ RSpec.describe API::Repositories do
expect(type).to eq('git-archive')
expect(params['ArchivePath']).to match(/#{project.path}\-[^\.]+\.tar.gz/)
+ expect(response.parsed_body).to be_empty
end
it 'returns the repository archive archive.zip' do
@@ -495,6 +497,43 @@ RSpec.describe API::Repositories do
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ it "returns a newly created commit", :use_clean_rails_redis_caching do
+ # Parse the commits ourselves because json_response is cached
+ def commit_messages(response)
+ Gitlab::Json.parse(response.body)["commits"].map do |commit|
+ commit["message"]
+ end
+ end
+
+ # First trigger the rate limit cache
+ get api(route, current_user), params: { from: 'master', to: 'feature' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(commit_messages(response)).not_to include("Cool new commit")
+
+ # Then create a new commit via the API
+ post api("/projects/#{project.id}/repository/commits", user), params: {
+ branch: "feature",
+ commit_message: "Cool new commit",
+ actions: [
+ {
+ action: "create",
+ file_path: "foo/bar/baz.txt",
+ content: "puts 8"
+ }
+ ]
+ }
+
+ expect(response).to have_gitlab_http_status(:created)
+
+ # Now perform the same query as before, but the cache should have expired
+ # and our new commit should exist
+ get api(route, current_user), params: { from: 'master', to: 'feature' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(commit_messages(response)).to include("Cool new commit")
+ end
end
context 'when unauthenticated', 'and project is public' do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 423e19c3971..641c6a2cd91 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -612,5 +612,46 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do
expect(json_response.slice(*settings.keys)).to eq(settings)
end
end
+
+ context 'Sentry settings' do
+ let(:settings) do
+ {
+ sentry_enabled: true,
+ sentry_dsn: 'http://sentry.example.com',
+ sentry_clientside_dsn: 'http://sentry.example.com',
+ sentry_environment: 'production'
+ }
+ end
+
+ let(:attribute_names) { settings.keys.map(&:to_s) }
+
+ it 'includes the attributes in the API' do
+ get api('/application/settings', admin)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ attribute_names.each do |attribute|
+ expect(json_response.keys).to include(attribute)
+ end
+ end
+
+ it 'allows updating the settings' do
+ put api('/application/settings', admin), params: settings
+
+ expect(response).to have_gitlab_http_status(:ok)
+ settings.each do |attribute, value|
+ expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
+ end
+ end
+
+ context 'missing sentry_dsn value when sentry_enabled is true' do
+ it 'returns a blank parameter error message' do
+ put api('/application/settings', admin), params: { sentry_enabled: true }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ message = json_response['message']
+ expect(message["sentry_dsn"]).to include(a_string_matching("can't be blank"))
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index f4d15d0525e..dd5e6ac8a5e 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -113,6 +113,7 @@ RSpec.describe API::Snippets, factory_default: :keep do
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq 'text/plain'
expect(headers['Content-Disposition']).to match(/^inline/)
+ expect(response.parsed_body).to be_empty
end
it 'returns 404 for invalid snippet id' do
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 1aa1ad87be9..bb56192a2ff 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -17,6 +17,10 @@ RSpec.describe API::Tags do
end
describe 'GET /projects/:id/repository/tags' do
+ before do
+ stub_feature_flags(tag_list_keyset_pagination: false)
+ end
+
shared_examples "get repository tags" do
let(:route) { "/projects/#{project_id}/repository/tags" }
@@ -143,6 +147,55 @@ RSpec.describe API::Tags do
expect(expected_tag['release']['description']).to eq(description)
end
end
+
+ context 'with keyset pagination on', :aggregate_errors do
+ before do
+ stub_feature_flags(tag_list_keyset_pagination: true)
+ end
+
+ context 'with keyset pagination option' do
+ let(:base_params) { { pagination: 'keyset' } }
+
+ context 'with gitaly pagination params' do
+ context 'with high limit' do
+ let(:params) { base_params.merge(per_page: 100) }
+
+ it 'returns all repository tags' do
+ get api(route, user), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/tags')
+ expect(response.headers).not_to include('Link')
+ tag_names = json_response.map { |x| x['name'] }
+ expect(tag_names).to match_array(project.repository.tag_names)
+ end
+ end
+
+ context 'with low limit' do
+ let(:params) { base_params.merge(per_page: 2) }
+
+ it 'returns limited repository tags' do
+ get api(route, user), params: params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/tags')
+ expect(response.headers).to include('Link')
+ tag_names = json_response.map { |x| x['name'] }
+ expect(tag_names).to match_array(%w(v1.1.0 v1.1.1))
+ end
+ end
+
+ context 'with missing page token' do
+ let(:params) { base_params.merge(page_token: 'unknown') }
+
+ it_behaves_like '422 response' do
+ let(:request) { get api(route, user), params: params }
+ let(:message) { 'Invalid page token: refs/tags/unknown' }
+ end
+ end
+ end
+ end
+ end
end
context ":api_caching_tags flag enabled", :use_clean_rails_memory_store_caching do
@@ -208,6 +261,20 @@ RSpec.describe API::Tags do
it_behaves_like "get repository tags"
end
+
+ context 'when gitaly is unavailable' do
+ let(:route) { "/projects/#{project_id}/repository/tags" }
+
+ before do
+ expect_next_instance_of(TagsFinder) do |finder|
+ allow(finder).to receive(:execute).and_raise(Gitlab::Git::CommandError)
+ end
+ end
+
+ it_behaves_like '503 response' do
+ let(:request) { get api(route, user) }
+ end
+ end
end
describe 'GET /projects/:id/repository/tags/:tag_name' do
diff --git a/spec/requests/api/terraform/modules/v1/packages_spec.rb b/spec/requests/api/terraform/modules/v1/packages_spec.rb
index b04f5ad9a94..b17bc11a451 100644
--- a/spec/requests/api/terraform/modules/v1/packages_spec.rb
+++ b/spec/requests/api/terraform/modules/v1/packages_spec.rb
@@ -28,10 +28,25 @@ RSpec.describe API::Terraform::Modules::V1::Packages do
describe 'GET /api/v4/packages/terraform/modules/v1/:module_namespace/:module_name/:module_system/versions' do
let(:url) { api("/packages/terraform/modules/v1/#{group.path}/#{package.name}/versions") }
- let(:headers) { {} }
+ let(:headers) { { 'Authorization' => "Bearer #{tokens[:job_token]}" } }
subject { get(url, headers: headers) }
+ context 'with a conflicting package name' do
+ let!(:conflicting_package) { create(:terraform_module_package, project: project, name: "conflict-#{package.name}", version: '2.0.0') }
+
+ before do
+ group.add_developer(user)
+ end
+
+ it 'returns only one version' do
+ subject
+
+ expect(json_response['modules'][0]['versions'].size).to eq(1)
+ expect(json_response['modules'][0]['versions'][0]['version']).to eq('1.0.0')
+ end
+ end
+
context 'with valid namespace' do
where(:visibility, :user_role, :member, :token_type, :valid_token, :shared_examples_name, :expected_status) do
:public | :developer | true | :personal_access_token | true | 'returns terraform module packages' | :success
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index d31f571e636..c9deb84ff98 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -13,6 +13,8 @@ RSpec.describe API::Todos do
let_it_be(:john_doe) { create(:user, username: 'john_doe') }
let_it_be(:issue) { create(:issue, project: project_1) }
let_it_be(:merge_request) { create(:merge_request, source_project: project_1) }
+ let_it_be(:alert) { create(:alert_management_alert, project: project_1) }
+ let_it_be(:alert_todo) { create(:todo, project: project_1, author: john_doe, user: john_doe, target: alert) }
let_it_be(:merge_request_todo) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) }
let_it_be(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe, target: issue) }
let_it_be(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe, target: issue) }
@@ -67,7 +69,7 @@ RSpec.describe API::Todos do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
+ expect(json_response.length).to eq(5)
expect(json_response[0]['id']).to eq(pending_3.id)
expect(json_response[0]['project']).to be_a Hash
expect(json_response[0]['author']).to be_a Hash
@@ -95,6 +97,10 @@ RSpec.describe API::Todos do
expect(json_response[3]['target']['merge_requests_count']).to be_nil
expect(json_response[3]['target']['upvotes']).to eq(1)
expect(json_response[3]['target']['downvotes']).to eq(0)
+
+ expect(json_response[4]['target_type']).to eq('AlertManagement::Alert')
+ expect(json_response[4]['target']['iid']).to eq(alert.iid)
+ expect(json_response[4]['target']['title']).to eq(alert.title)
end
context "when current user does not have access to one of the TODO's target" do
@@ -105,7 +111,7 @@ RSpec.describe API::Todos do
get api('/todos', john_doe)
- expect(json_response.count).to eq(4)
+ expect(json_response.count).to eq(5)
expect(json_response.map { |t| t['id'] }).not_to include(no_access_todo.id, pending_4.id)
end
end
@@ -163,7 +169,7 @@ RSpec.describe API::Todos do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(3)
+ expect(json_response.length).to eq(4)
end
end
diff --git a/spec/requests/api/topics_spec.rb b/spec/requests/api/topics_spec.rb
new file mode 100644
index 00000000000..a5746a4022e
--- /dev/null
+++ b/spec/requests/api/topics_spec.rb
@@ -0,0 +1,217 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Topics do
+ include WorkhorseHelpers
+
+ let_it_be(:topic_1) { create(:topic, name: 'Git', total_projects_count: 1) }
+ let_it_be(:topic_2) { create(:topic, name: 'GitLab', total_projects_count: 2) }
+ let_it_be(:topic_3) { create(:topic, name: 'other-topic', total_projects_count: 3) }
+
+ let_it_be(:admin) { create(:user, :admin) }
+ let_it_be(:user) { create(:user) }
+
+ let(:file) { fixture_file_upload('spec/fixtures/dk.png') }
+
+ describe 'GET /topics', :aggregate_failures do
+ it 'returns topics ordered by total_projects_count' do
+ get api('/topics')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+
+ expect(json_response[0]['id']).to eq(topic_3.id)
+ expect(json_response[0]['name']).to eq('other-topic')
+ expect(json_response[0]['total_projects_count']).to eq(3)
+
+ expect(json_response[1]['id']).to eq(topic_2.id)
+ expect(json_response[1]['name']).to eq('GitLab')
+ expect(json_response[1]['total_projects_count']).to eq(2)
+
+ expect(json_response[2]['id']).to eq(topic_1.id)
+ expect(json_response[2]['name']).to eq('Git')
+ expect(json_response[2]['total_projects_count']).to eq(1)
+ end
+
+ context 'with search' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:search, :result) do
+ '' | %w[other-topic GitLab Git]
+ 'g' | %w[]
+ 'gi' | %w[]
+ 'git' | %w[Git GitLab]
+ 'x' | %w[]
+ 0 | %w[]
+ end
+
+ with_them do
+ it 'returns filtered topics' do
+ get api('/topics'), params: { search: search }
+
+ expect(json_response.map { |t| t['name'] }).to eq(result)
+ end
+ end
+ end
+
+ context 'with pagination' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:params, :result) do
+ { page: 0 } | %w[other-topic GitLab Git]
+ { page: 1 } | %w[other-topic GitLab Git]
+ { page: 2 } | %w[]
+ { per_page: 1 } | %w[other-topic]
+ { per_page: 2 } | %w[other-topic GitLab]
+ { per_page: 3 } | %w[other-topic GitLab Git]
+ { page: 0, per_page: 1 } | %w[other-topic]
+ { page: 0, per_page: 2 } | %w[other-topic GitLab]
+ { page: 1, per_page: 1 } | %w[other-topic]
+ { page: 1, per_page: 2 } | %w[other-topic GitLab]
+ { page: 2, per_page: 1 } | %w[GitLab]
+ { page: 2, per_page: 2 } | %w[Git]
+ { page: 3, per_page: 1 } | %w[Git]
+ { page: 3, per_page: 2 } | %w[]
+ { page: 4, per_page: 1 } | %w[]
+ { page: 4, per_page: 2 } | %w[]
+ end
+
+ with_them do
+ it 'returns paginated topics' do
+ get api('/topics'), params: params
+
+ expect(json_response.map { |t| t['name'] }).to eq(result)
+ end
+ end
+ end
+ end
+
+ describe 'GET /topic/:id', :aggregate_failures do
+ it 'returns topic' do
+ get api("/topics/#{topic_2.id}")
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ expect(json_response['id']).to eq(topic_2.id)
+ expect(json_response['name']).to eq('GitLab')
+ expect(json_response['total_projects_count']).to eq(2)
+ end
+
+ it 'returns 404 for non existing id' do
+ get api("/topics/#{non_existing_record_id}")
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 400 for invalid `id` parameter' do
+ get api('/topics/invalid')
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eql('id is invalid')
+ end
+ end
+
+ describe 'POST /topics', :aggregate_failures do
+ context 'as administrator' do
+ it 'creates a topic' do
+ post api('/topics/', admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['name']).to eq('my-topic')
+ expect(Projects::Topic.find(json_response['id']).name).to eq('my-topic')
+ end
+
+ it 'creates a topic with avatar and description' do
+ workhorse_form_with_file(
+ api('/topics/', admin),
+ file_key: :avatar,
+ params: { name: 'my-topic', description: 'my description...', avatar: file }
+ )
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['description']).to eq('my description...')
+ expect(json_response['avatar_url']).to end_with('dk.png')
+ end
+
+ it 'returns 400 if name is missing' do
+ post api('/topics/', admin)
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eql('name is missing')
+ end
+ end
+
+ context 'as normal user' do
+ it 'returns 403 Forbidden' do
+ post api('/topics/', user), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'as anonymous' do
+ it 'returns 401 Unauthorized' do
+ post api('/topics/'), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+
+ describe 'PUT /topics', :aggregate_failures do
+ context 'as administrator' do
+ it 'updates a topic' do
+ put api("/topics/#{topic_3.id}", admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq('my-topic')
+ expect(topic_3.reload.name).to eq('my-topic')
+ end
+
+ it 'updates a topic with avatar and description' do
+ workhorse_form_with_file(
+ api("/topics/#{topic_3.id}", admin),
+ method: :put,
+ file_key: :avatar,
+ params: { description: 'my description...', avatar: file }
+ )
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['description']).to eq('my description...')
+ expect(json_response['avatar_url']).to end_with('dk.png')
+ end
+
+ it 'returns 404 for non existing id' do
+ put api("/topics/#{non_existing_record_id}", admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 400 for invalid `id` parameter' do
+ put api('/topics/invalid', admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eql('id is invalid')
+ end
+ end
+
+ context 'as normal user' do
+ it 'returns 403 Forbidden' do
+ put api("/topics/#{topic_3.id}", user), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'as anonymous' do
+ it 'returns 401 Unauthorized' do
+ put api("/topics/#{topic_3.id}"), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index fb01845b63a..b93df2f3bae 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1464,6 +1464,7 @@ RSpec.describe API::Users do
credit_card_expiration_year: expiration_year,
credit_card_expiration_month: 1,
credit_card_holder_name: 'John Smith',
+ credit_card_type: 'AmericanExpress',
credit_card_mask_number: '1111'
}
end
@@ -1495,6 +1496,7 @@ RSpec.describe API::Users do
credit_card_validated_at: credit_card_validated_time,
expiration_date: Date.new(expiration_year, 1, 31),
last_digits: 1111,
+ network: 'AmericanExpress',
holder_name: 'John Smith'
)
end
@@ -1904,7 +1906,8 @@ RSpec.describe API::Users do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.first['email']).to eq(email.email)
+ expect(json_response.first['email']).to eq(user.email)
+ expect(json_response.second['email']).to eq(email.email)
end
it "returns a 404 for invalid ID" do
@@ -2486,7 +2489,8 @@ RSpec.describe API::Users do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.first["email"]).to eq(email.email)
+ expect(json_response.first['email']).to eq(user.email)
+ expect(json_response.second['email']).to eq(email.email)
end
context "scopes" do
diff --git a/spec/requests/api/v3/github_spec.rb b/spec/requests/api/v3/github_spec.rb
index 255f53e4c7c..6d8ae226ce4 100644
--- a/spec/requests/api/v3/github_spec.rb
+++ b/spec/requests/api/v3/github_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe API::V3::Github do
let_it_be(:user) { create(:user) }
let_it_be(:unauthorized_user) { create(:user) }
let_it_be(:admin) { create(:user, :admin) }
- let_it_be(:project) { create(:project, :repository, creator: user) }
+ let_it_be_with_reload(:project) { create(:project, :repository, creator: user) }
before do
project.add_maintainer(user)
@@ -506,11 +506,18 @@ RSpec.describe API::V3::Github do
describe 'GET /repos/:namespace/:project/commits/:sha' do
let(:commit) { project.repository.commit }
- let(:commit_id) { commit.id }
+
+ def call_api(commit_id: commit.id)
+ jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/commits/#{commit_id}", user)
+ end
+
+ def response_diff_files(response)
+ Gitlab::Json.parse(response.body)['files']
+ end
context 'authenticated' do
- it 'returns commit with github format' do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/commits/#{commit_id}", user)
+ it 'returns commit with github format', :aggregate_failures do
+ call_api
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('entities/github/commit')
@@ -519,36 +526,130 @@ RSpec.describe API::V3::Github do
it 'returns 200 when project path include a dot' do
project.update!(path: 'foo.bar')
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/commits/#{commit_id}", user)
+ call_api
expect(response).to have_gitlab_http_status(:ok)
end
- it 'returns 200 when namespace path include a dot' do
- group = create(:group, path: 'foo.bar')
- project = create(:project, :repository, group: group)
- project.add_reporter(user)
+ context 'when namespace path includes a dot' do
+ let(:group) { create(:group, path: 'foo.bar') }
+ let(:project) { create(:project, :repository, group: group) }
- jira_get v3_api("/repos/#{group.path}/#{project.path}/commits/#{commit_id}", user)
+ it 'returns 200 when namespace path include a dot' do
+ project.add_reporter(user)
- expect(response).to have_gitlab_http_status(:ok)
+ call_api
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when the Gitaly `CommitDiff` RPC times out', :use_clean_rails_memory_store_caching do
+ let(:commit_diff_args) { [project.repository_storage, :diff_service, :commit_diff, any_args] }
+
+ before do
+ allow(Gitlab::GitalyClient).to receive(:call)
+ .and_call_original
+ end
+
+ it 'handles the error, logs it, and returns empty diff files', :aggregate_failures do
+ allow(Gitlab::GitalyClient).to receive(:call)
+ .with(*commit_diff_args)
+ .and_raise(GRPC::DeadlineExceeded)
+
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with an_instance_of(GRPC::DeadlineExceeded)
+
+ call_api
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response_diff_files(response)).to be_blank
+ end
+
+ it 'does not handle the error when feature flag is disabled', :aggregate_failures do
+ stub_feature_flags(api_v3_commits_skip_diff_files: false)
+
+ allow(Gitlab::GitalyClient).to receive(:call)
+ .with(*commit_diff_args)
+ .and_raise(GRPC::DeadlineExceeded)
+
+ call_api
+
+ expect(response).to have_gitlab_http_status(:error)
+ end
+
+ it 'only calls Gitaly once for all attempts within a period of time', :aggregate_failures do
+ expect(Gitlab::GitalyClient).to receive(:call)
+ .with(*commit_diff_args)
+ .once # <- once
+ .and_raise(GRPC::DeadlineExceeded)
+
+ 3.times do
+ call_api
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response_diff_files(response)).to be_blank
+ end
+ end
+
+ it 'calls Gitaly again after a period of time', :aggregate_failures do
+ expect(Gitlab::GitalyClient).to receive(:call)
+ .with(*commit_diff_args)
+ .twice # <- twice
+ .and_raise(GRPC::DeadlineExceeded)
+
+ call_api
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response_diff_files(response)).to be_blank
+
+ travel_to((described_class::GITALY_TIMEOUT_CACHE_EXPIRY + 1.second).from_now) do
+ call_api
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response_diff_files(response)).to be_blank
+ end
+ end
+
+ it 'uses a unique cache key, allowing other calls to succeed' do
+ cache_key = [described_class::GITALY_TIMEOUT_CACHE_KEY, project.id, commit.cache_key].join(':')
+ Rails.cache.write(cache_key, 1)
+
+ expect(Gitlab::GitalyClient).to receive(:call)
+ .with(*commit_diff_args)
+ .once # <- once
+
+ call_api
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response_diff_files(response)).to be_blank
+
+ call_api(commit_id: commit.parent.id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response_diff_files(response).length).to eq(1)
+ end
end
end
context 'unauthenticated' do
+ let(:user) { nil }
+
it 'returns 401' do
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/commits/#{commit_id}", nil)
+ call_api
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'unauthorized' do
+ let(:user) { unauthorized_user }
+
it 'returns 404 when lower access level' do
- project.add_guest(unauthorized_user)
+ project.add_guest(user)
- jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/commits/#{commit_id}",
- unauthorized_user)
+ call_api
expect(response).to have_gitlab_http_status(:not_found)
end