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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 15:26:25 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 15:26:25 +0300
commita09983ae35713f5a2bbb100981116d31ce99826e (patch)
tree2ee2af7bd104d57086db360a7e6d8c9d5d43667a /spec/support/shared_examples/requests
parent18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff)
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'spec/support/shared_examples/requests')
-rw-r--r--spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb138
-rw-r--r--spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb408
-rw-r--r--spec/support/shared_examples/requests/api/packages_shared_examples.rb43
-rw-r--r--spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb185
-rw-r--r--spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb152
-rw-r--r--spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb37
-rw-r--r--spec/support/shared_examples/requests/api/snippets_shared_examples.rb79
-rw-r--r--spec/support/shared_examples/requests/snippet_shared_examples.rb24
10 files changed, 1069 insertions, 9 deletions
diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
new file mode 100644
index 00000000000..5257980d7df
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'Composer user type' do |user_type, add_member|
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+end
+
+RSpec.shared_examples 'Composer package index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/index')
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer empty provider index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/provider')
+ expect(json_response['providers']).to eq({})
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer provider index' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/provider')
+ expect(json_response['providers']).to include(package.name)
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer package api request' do |user_type, status, add_member = true|
+ include_context 'Composer user type', user_type, add_member do
+ it 'returns the package index' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ expect(response).to match_response_schema('public_api/v4/packages/composer/package')
+ expect(json_response['packages']).to include(package.name)
+ expect(json_response['packages'][package.name]).to include(package.version)
+ end
+ end
+end
+
+RSpec.shared_examples 'Composer package creation' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'creates package files' do
+ expect { subject }
+ .to change { project.packages.composer.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'register_package'
+ end
+end
+
+RSpec.shared_examples 'process Composer api request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_context 'Composer auth headers' do |user_role, user_token|
+ let(:token) { user_token ? personal_access_token.token : 'wrong' }
+ let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+end
+
+RSpec.shared_context 'Composer api project access' do |project_visibility_level, user_role, user_token|
+ include_context 'Composer auth headers', user_role, user_token do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+ end
+end
+
+RSpec.shared_context 'Composer api group access' do |project_visibility_level, user_role, user_token|
+ include_context 'Composer auth headers', user_role, user_token do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects Composer access with unknown group id' do
+ context 'with an unknown group' do
+ let(:group) { double(id: non_existing_record_id) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects Composer access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { double(id: non_existing_record_id) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process Composer api request', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
index 246f1850c3c..da1caef63ba 100644
--- a/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/projects/services_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-shared_examples 'unauthorized users cannot read services' do
+RSpec.shared_examples 'unauthorized users cannot read services' do
before do
post_graphql(query, current_user: current_user)
end
diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
index 60ed61269df..a34c48a5ba4 100644
--- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb
@@ -132,6 +132,16 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
expect(response).to have_gitlab_http_status(:created)
expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_falsey
+ expect(json_response['author']['username']).to eq(user.username)
+ end
+
+ it "creates a confidential note if confidential is set to true" do
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!', confidential: true }
+
+ expect(response).to have_gitlab_http_status(:created)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['confidential']).to be_truthy
expect(json_response['author']['username']).to eq(user.username)
end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
new file mode 100644
index 00000000000..8d8483cae72
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -0,0 +1,408 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects nuget packages access' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ if status == :unauthorized
+ it 'has the correct response header' do
+ subject
+
+ expect(response.headers['Www-Authenticate: Basic realm']).to eq 'GitLab Packages Registry'
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget service index request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'nuget_service_index'
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/service_index')
+ expect(json_response).to be_a(Hash)
+ end
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+ end
+end
+
+RSpec.shared_examples 'returning nuget metadata json response with json schema' do |json_schema|
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema(json_schema)
+ expect(json_response).to be_a(Hash)
+ end
+end
+
+RSpec.shared_examples 'process nuget metadata request at package name level' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget metadata request at package name and package version level' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'has the proper content type' do
+ subject
+
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ context 'with a request that bypassed gitlab-workhorse' do
+ let(:headers) do
+ build_basic_auth_header(user.username, personal_access_token.token)
+ .merge(workhorse_header)
+ .tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) }
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creates nuget package files' do
+ it 'creates package files' do
+ expect(::Packages::Nuget::ExtractionWorker).to receive(:perform_async).once
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq('package.nupkg')
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creates nuget package files'
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { { package: fog_file, 'package.remote_id' => file_name } }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creates nuget package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ package: fog_file,
+ 'package.remote_id' => remote_id
+ }
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+
+ context 'with crafted package.path param' do
+ let(:crafted_file) { Tempfile.new('nuget.crafted.package.path') }
+ let(:url) { "/projects/#{project.id}/packages/nuget?package.path=#{crafted_file.path}" }
+ let(:params) { { file: temp_file(file_name) } }
+ let(:file_key) { :file }
+
+ it 'does not create a package file' do
+ expect { subject }.to change { ::Packages::PackageFile.count }.by(0)
+ end
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creates nuget package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creates nuget package files'
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+end
+
+RSpec.shared_examples 'process nuget download versions request' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'returns a valid nuget download versions json response' do
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/download_versions')
+ expect(json_response).to be_a(Hash)
+ expect(json_response['versions']).to match_array(packages.map(&:version).sort)
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returns a valid nuget download versions json response'
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'returns a valid nuget download versions json response'
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget download content request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+
+ it 'returns a valid package archive' do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ context 'with invalid format' do
+ let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.xls" }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+
+ context 'with lower case package name' do
+ let_it_be(:package_name) { 'dummy.package' }
+
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid package archive' do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'process nuget search request' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'returns a valid json search response' do |status, total_hits, versions|
+ it_behaves_like 'returning response status', status
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to be_a(Hash)
+ expect(json_response).to match_schema('public_api/v4/packages/nuget/search')
+ expect(json_response['totalHits']).to eq total_hits
+ expect(json_response['data'].map { |e| e['versions'].size }).to match_array(versions)
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returns a valid json search response', status, 4, [1, 5, 5, 1]
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'search_package'
+
+ context 'with skip set to 2' do
+ let(:skip) { 2 }
+
+ it_behaves_like 'returns a valid json search response', status, 4, [5, 1]
+ end
+
+ context 'with take set to 2' do
+ let(:take) { 2 }
+
+ it_behaves_like 'returns a valid json search response', status, 4, [1, 5]
+ end
+
+ context 'without prereleases' do
+ let(:include_prereleases) { false }
+
+ it_behaves_like 'returns a valid json search response', status, 3, [1, 5, 5]
+ end
+
+ context 'with empty search term' do
+ let(:search_term) { '' }
+
+ it_behaves_like 'returns a valid json search response', status, 5, [1, 5, 5, 1, 1]
+ end
+
+ context 'with nil search term' do
+ let(:search_term) { nil }
+
+ it_behaves_like 'returns a valid json search response', status, 5, [1, 5, 5, 1, 1]
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects nuget access with invalid project id' do
+ context 'with a project id with invalid integers' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:project) { OpenStruct.new(id: id) }
+
+ where(:id, :status) do
+ '/../' | :unauthorized
+ '' | :not_found
+ '%20' | :unauthorized
+ '%2e%2e%2f' | :unauthorized
+ 'NaN' | :unauthorized
+ 00002345 | :unauthorized
+ 'anything25' | :unauthorized
+ end
+
+ with_them do
+ it_behaves_like 'rejects nuget packages access', :anonymous, params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'rejects nuget access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { OpenStruct.new(id: 1234567890) }
+
+ context 'as anonymous' do
+ it_behaves_like 'rejects nuget packages access', :anonymous, :unauthorized
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
new file mode 100644
index 00000000000..ec15d7a4d2e
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'deploy token for package GET requests' do
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token) }
+
+ subject { get api(url), headers: headers }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, 'bar') }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+end
+
+RSpec.shared_examples 'deploy token for package uploads' do
+ context 'with deploy token headers' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'valid token' do
+ it_behaves_like 'returning response status', :success
+ end
+
+ context 'invalid token' do
+ let(:headers) { build_basic_auth_header(deploy_token.username, 'bar').merge(workhorse_header) }
+
+ it_behaves_like 'returning response status', :unauthorized
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
new file mode 100644
index 00000000000..a371d380f47
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb
@@ -0,0 +1,185 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects package tags access' do |user_type, status|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'returns package tags' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ stub_application_setting(npm_package_requests_forwarding: false)
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', :success
+
+ it 'returns a valid json response' do
+ subject
+
+ expect(response.media_type).to eq('application/json')
+ expect(json_response).to be_a(Hash)
+ end
+
+ it 'returns two package tags' do
+ subject
+
+ expect(json_response).to match_schema('public_api/v4/packages/npm_package_tags')
+ expect(json_response.length).to eq(3) # two tags + latest (auto added)
+ expect(json_response[package_tag1.name]).to eq(package.version)
+ expect(json_response[package_tag2.name]).to eq(package.version)
+ expect(json_response['latest']).to eq(package.version)
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ '%20' | :bad_request
+ nil | :forbidden
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'create package tag' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ it_behaves_like 'returning response status', :no_content
+
+ it 'creates the package tag' do
+ expect { subject }.to change { Packages::Tag.count }.by(1)
+
+ last_tag = Packages::Tag.last
+ expect(last_tag.name).to eq(tag_name)
+ expect(last_tag.package).to eq(package)
+ end
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+
+ context 'with already existing tag' do
+ let_it_be(:package2) { create(:npm_package, project: project, name: package.name, version: '5.5.55') }
+ let_it_be(:tag) { create(:packages_tag, package: package2, name: tag_name) }
+
+ it_behaves_like 'returning response status', :no_content
+
+ it 'reuses existing tag' do
+ expect(package.tags).to be_empty
+ expect(package2.tags).to eq([tag])
+ expect { subject }.to not_change { Packages::Tag.count }
+ expect(package.reload.tags).to eq([tag])
+ expect(package2.reload.tags).to be_empty
+ end
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ 'unknown' | :forbidden
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid tag name' do
+ where(:tag_name, :status) do
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid version' do
+ where(:version, :status) do
+ ' ' | :bad_request
+ '' | :bad_request
+ nil | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+end
+
+RSpec.shared_examples 'delete package tag' do |user_type|
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.send("add_#{user_type}", user) unless user_type == :no_type
+ end
+
+ context "for #{user_type} user" do
+ it_behaves_like 'returning response status', :no_content
+
+ it 'returns a valid response' do
+ subject
+
+ expect(response.body).to be_empty
+ end
+
+ it 'destroy the package tag' do
+ expect(package.tags).to eq([package_tag])
+ expect { subject }.to change { Packages::Tag.count }.by(-1)
+ expect(package.reload.tags).to be_empty
+ end
+
+ context 'with tag from other package' do
+ let(:package2) { create(:npm_package, project: project) }
+ let(:package_tag) { create(:packages_tag, package: package2) }
+
+ it_behaves_like 'returning response status', :not_found
+ end
+
+ context 'with invalid package name' do
+ where(:package_name, :status) do
+ 'unknown' | :forbidden
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+
+ context 'with invalid tag name' do
+ where(:tag_name, :status) do
+ 'unknown' | :not_found
+ '' | :not_found
+ '%20' | :bad_request
+ end
+
+ with_them do
+ it_behaves_like 'returning response status', params[:status]
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
new file mode 100644
index 00000000000..fcc166ac87d
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'PyPi package creation' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creating pypi package files' do
+ it 'creates package files' do
+ expect { subject }
+ .to change { project.packages.pypi.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ .and change { Packages::Pypi::Metadatum.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package = project.reload.packages.pypi.last
+
+ expect(package.name).to eq params[:name]
+ expect(package.version).to eq params[:version]
+ expect(package.pypi_metadatum.required_python).to eq params[:requires_python]
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'creating pypi package files'
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creating pypi package files'
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create(
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { base_params.merge(content: fog_file, 'content.remote_id' => file_name) }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creating pypi package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) { base_params.merge(content: fog_file, 'content.remote_id' => remote_id) }
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creating pypi package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creating pypi package files'
+ end
+ end
+ end
+
+ it_behaves_like 'background upload schedules a file migration'
+ end
+end
+
+RSpec.shared_examples 'PyPi package versions' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'returns the package listing' do
+ subject
+
+ expect(response.body).to match(package.package_files.first.file_name)
+ end
+
+ it_behaves_like 'returning response status', status
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'list_package'
+ end
+end
+
+RSpec.shared_examples 'PyPi package download' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'returns the package listing' do
+ subject
+
+ expect(response.body).to eq(File.open(package.package_files.first.file.path, "rb").read)
+ end
+
+ it_behaves_like 'returning response status', status
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+end
+
+RSpec.shared_examples 'process PyPi api request' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'rejects PyPI access with unknown project id' do
+ context 'with an unknown project' do
+ let(:project) { OpenStruct.new(id: 1234567890) }
+
+ context 'as anonymous' do
+ it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ end
+
+ context 'as authenticated user' do
+ subject { get api(url), headers: build_basic_auth_header(user.username, personal_access_token.token) }
+
+ it_behaves_like 'process PyPi api request', :anonymous, :not_found
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
index bca51dab353..d21a9f419fd 100644
--- a/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/resource_milestone_events_api_shared_examples.rb
@@ -16,6 +16,29 @@ RSpec.shared_examples 'resource_milestone_events API' do |parent_type, eventable
expect(json_response.first['action']).to eq(event.action)
end
+ context 'when there is an event with a milestone which is not visible for requesting user' do
+ let!(:private_project) { create(:project, :private) }
+ let!(:private_milestone) { create(:milestone, project: private_project) }
+
+ let!(:other_user) { create(:user) }
+
+ it 'returns the expected events' do
+ create_event(private_milestone)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events", other_user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq('1')
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+
+ expect(json_response.first['id']).to eq(event.id)
+ expect(json_response.first['milestone']['id']).to eq(event.milestone.id)
+ expect(json_response.first['action']).to eq(event.action)
+ end
+ end
+
it "returns a 404 error when eventable id not found" do
get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{non_existing_record_id}/resource_milestone_events", user)
@@ -60,6 +83,20 @@ RSpec.shared_examples 'resource_milestone_events API' do |parent_type, eventable
end
end
+ describe 'pagination' do
+ let!(:event1) { create_event(milestone) }
+ let!(:event2) { create_event(milestone) }
+
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/220192
+ it 'returns the second page' do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_milestone_events?page=2&per_page=1", user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['id']).to eq(event2.id)
+ end
+ end
+
def create_event(milestone, action: :add)
create(:resource_milestone_event, eventable.class.name.underscore => eventable, milestone: milestone, action: action)
end
diff --git a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
new file mode 100644
index 00000000000..cfbb84dd099
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'raw snippet files' do
+ let_it_be(:unauthorized_user) { create(:user) }
+ let(:snippet_id) { snippet.id }
+ let(:user) { snippet.author }
+ let(:file_path) { '%2Egitattributes' }
+ let(:ref) { 'master' }
+
+ context 'with no user' do
+ it 'requires authentication' do
+ get api(api_path)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ shared_examples 'not found' do
+ it 'returns 404' do
+ get api(api_path, user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Snippet Not Found')
+ end
+ end
+
+ context 'when not authorized' do
+ let(:user) { unauthorized_user }
+
+ it_behaves_like 'not found'
+ end
+
+ context 'with an invalid snippet ID' do
+ let(:snippet_id) { 'invalid' }
+
+ it_behaves_like 'not found'
+ end
+
+ context 'with valid params' do
+ it 'returns the raw file info' do
+ expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original
+
+ get api(api_path, user)
+
+ aggregate_failures do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq 'text/plain'
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ expect(response.header['Content-Disposition']).to match 'filename=".gitattributes"'
+ end
+ end
+ end
+
+ context 'with invalid params' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:file_path, :ref, :status, :key, :message) do
+ '%2Egitattributes' | 'invalid-ref' | :not_found | 'message' | '404 Reference Not Found'
+ '%2Egitattributes' | nil | :not_found | 'error' | '404 Not Found'
+ '%2Egitattributes' | '' | :not_found | 'error' | '404 Not Found'
+
+ 'doesnotexist.rb' | 'master' | :not_found | 'message' | '404 File Not Found'
+ '/does/not/exist.rb' | 'master' | :not_found | 'error' | '404 Not Found'
+ '%2E%2E%2Fetc%2Fpasswd' | 'master' | :bad_request | 'error' | 'file_path should be a valid file path'
+ '%2Fetc%2Fpasswd' | 'master' | :bad_request | 'error' | 'file_path should be a valid file path'
+ '../../etc/passwd' | 'master' | :not_found | 'error' | '404 Not Found'
+ end
+
+ with_them do
+ before do
+ get api(api_path, user)
+ end
+
+ it { expect(response).to have_gitlab_http_status(status) }
+ it { expect(json_response[key]).to eq(message) }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/snippet_shared_examples.rb b/spec/support/shared_examples/requests/snippet_shared_examples.rb
index f830f957174..644abb191a6 100644
--- a/spec/support/shared_examples/requests/snippet_shared_examples.rb
+++ b/spec/support/shared_examples/requests/snippet_shared_examples.rb
@@ -74,18 +74,14 @@ RSpec.shared_examples 'update with repository actions' do
end
end
-RSpec.shared_examples 'snippet response without repository URLs' do
- it 'skip inclusion of repository URLs' do
- expect(json_response).not_to have_key('ssh_url_to_repo')
- expect(json_response).not_to have_key('http_url_to_repo')
- end
-end
-
RSpec.shared_examples 'snippet blob content' do
it 'returns content from repository' do
+ expect(Gitlab::Workhorse).to receive(:send_git_blob).and_call_original
+
subject
- expect(response.body).to eq(snippet.blobs.first.data)
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
context 'when snippet repository is empty' do
@@ -98,3 +94,15 @@ RSpec.shared_examples 'snippet blob content' do
end
end
end
+
+RSpec.shared_examples 'snippet_multiple_files feature disabled' do
+ before do
+ stub_feature_flags(snippet_multiple_files: false)
+
+ subject
+ end
+
+ it 'does not return files attributes' do
+ expect(json_response).not_to have_key('files')
+ end
+end