From a09983ae35713f5a2bbb100981116d31ce99826e Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 20 Jul 2020 12:26:25 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-2-stable-ee --- .../api/composer_packages_shared_examples.rb | 138 +++++++ .../graphql/projects/services_shared_examples.rb | 2 +- .../requests/api/notes_shared_examples.rb | 10 + .../requests/api/nuget_packages_shared_examples.rb | 408 +++++++++++++++++++++ .../requests/api/packages_shared_examples.rb | 43 +++ .../requests/api/packages_tags_shared_examples.rb | 185 ++++++++++ .../requests/api/pypi_packages_shared_examples.rb | 152 ++++++++ ...esource_milestone_events_api_shared_examples.rb | 37 ++ .../requests/api/snippets_shared_examples.rb | 79 ++++ .../requests/snippet_shared_examples.rb | 24 +- 10 files changed, 1069 insertions(+), 9 deletions(-) create mode 100644 spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb create mode 100644 spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb create mode 100644 spec/support/shared_examples/requests/api/packages_shared_examples.rb create mode 100644 spec/support/shared_examples/requests/api/packages_tags_shared_examples.rb create mode 100644 spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb create mode 100644 spec/support/shared_examples/requests/api/snippets_shared_examples.rb (limited to 'spec/support/shared_examples/requests') 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 -- cgit v1.2.1