467 lines
16 KiB
Ruby
467 lines
16 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe API::ProjectPackages, feature_category: :package_registry do
|
|
let_it_be(:project) { create(:project, :public) }
|
|
|
|
let(:user) { create(:user) }
|
|
let!(:package1) { create(:npm_package, :last_downloaded_at, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
|
|
let(:package_url) { "/projects/#{project.id}/packages/#{package1.id}" }
|
|
let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
|
|
let!(:another_package) { create(:npm_package) }
|
|
let(:no_package_url) { "/projects/#{project.id}/packages/0" }
|
|
let(:wrong_package_url) { "/projects/#{project.id}/packages/#{another_package.id}" }
|
|
let(:params) { {} }
|
|
|
|
describe 'GET /projects/:id/packages' do
|
|
let(:url) { "/projects/#{project.id}/packages" }
|
|
let(:package_schema) { 'public_api/v4/packages/packages' }
|
|
|
|
subject { get api(url), params: params }
|
|
|
|
context 'without the need for a license' do
|
|
context 'project is public' do
|
|
it_behaves_like 'returns packages', :project, :no_type
|
|
end
|
|
|
|
context 'with conan package' do
|
|
let!(:conan_package) { create(:conan_package, project: project) }
|
|
|
|
it 'uses the conan recipe as the package name' do
|
|
subject
|
|
|
|
response_conan_package = json_response.find { |package| package['id'] == conan_package.id }
|
|
|
|
expect(response_conan_package['name']).to eq(conan_package.conan_recipe)
|
|
expect(response_conan_package['conan_package_name']).to eq(conan_package.name)
|
|
end
|
|
end
|
|
|
|
context 'with terraform module package' do
|
|
let_it_be(:terraform_module_package) { create(:terraform_module_package, project: project) }
|
|
|
|
context 'when no package_type filter is set' do
|
|
let(:params) { {} }
|
|
|
|
it 'filters out terraform module packages' do
|
|
subject
|
|
|
|
expect(json_response).not_to include(a_hash_including('package_type' => 'terraform_module'))
|
|
end
|
|
|
|
it 'returns packages with the package registry web_path' do
|
|
subject
|
|
|
|
expect(json_response).to include(a_hash_including('_links' => a_hash_including('web_path' => include('packages'))))
|
|
end
|
|
end
|
|
|
|
context 'when package_type filter is set to terraform_module' do
|
|
let(:params) { { package_type: :terraform_module } }
|
|
|
|
it 'returns the terraform module package' do
|
|
subject
|
|
|
|
expect(json_response).to include(a_hash_including('package_type' => 'terraform_module'))
|
|
end
|
|
|
|
it 'returns the terraform module package with the infrastructure registry web_path' do
|
|
subject
|
|
|
|
expect(json_response).to include(a_hash_including('_links' => a_hash_including('web_path' => include('infrastructure_registry'))))
|
|
end
|
|
end
|
|
|
|
context 'in nested group' do
|
|
let_it_be(:nested_project) { create(:project, :public, :in_subgroup) }
|
|
let_it_be(:nested_terraform_module_package) { create(:terraform_module_package, project: nested_project) }
|
|
|
|
let(:params) { { package_type: :terraform_module } }
|
|
let(:url) { "/projects/#{nested_project.id}/packages" }
|
|
|
|
it 'returns the nested terraform module package with the correct web_path' do
|
|
subject
|
|
|
|
expect(json_response).to include(a_hash_including('_links' => a_hash_including('web_path' => include(nested_project.namespace.full_path))))
|
|
end
|
|
end
|
|
|
|
context 'with JOB-TOKEN auth' do
|
|
let(:job) { create(:ci_build, :running, user: user, project: project) }
|
|
|
|
subject { get api(url, job_token: job.token) }
|
|
|
|
it_behaves_like 'returns packages', :project, :maintainer
|
|
it_behaves_like 'returns packages', :project, :developer
|
|
it_behaves_like 'returns packages', :project, :reporter
|
|
it_behaves_like 'returns packages', :project, :no_type
|
|
it_behaves_like 'returns packages', :project, :guest
|
|
end
|
|
end
|
|
|
|
context 'project is private' do
|
|
let(:project) { create(:project, :private) }
|
|
|
|
context 'for unauthenticated user' do
|
|
it_behaves_like 'rejects packages access', :project, :no_type, :not_found
|
|
end
|
|
|
|
context 'for authenticated user' do
|
|
subject { get api(url, user) }
|
|
|
|
it_behaves_like 'returns packages', :project, :maintainer
|
|
it_behaves_like 'returns packages', :project, :developer
|
|
it_behaves_like 'returns packages', :project, :reporter
|
|
it_behaves_like 'rejects packages access', :project, :no_type, :not_found
|
|
it_behaves_like 'rejects packages access', :project, :guest, :forbidden
|
|
|
|
context 'user is a maintainer' do
|
|
before do
|
|
project.add_maintainer(user)
|
|
end
|
|
|
|
it 'returns the destroy url' do
|
|
subject
|
|
|
|
expect(json_response.first['_links']).to include('delete_api_path')
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with JOB-TOKEN auth' do
|
|
let(:job) { create(:ci_build, :running, user: user, project: project) }
|
|
|
|
subject { get api(url, job_token: job.token) }
|
|
|
|
it_behaves_like 'returns packages', :project, :maintainer
|
|
it_behaves_like 'returns packages', :project, :developer
|
|
it_behaves_like 'returns packages', :project, :reporter
|
|
it_behaves_like 'rejects packages access', :project, :no_type, :not_found
|
|
# TODO uncomment when https://gitlab.com/gitlab-org/gitlab/-/issues/370998 is resolved
|
|
# it_behaves_like 'rejects packages access', :project, :guest, :not_found
|
|
end
|
|
end
|
|
|
|
context 'with pagination params' do
|
|
let!(:package3) { create(:maven_package, project: project) }
|
|
let!(:package4) { create(:maven_package, project: project) }
|
|
|
|
context 'with pagination params' do
|
|
let!(:package3) { create(:npm_package, project: project) }
|
|
let!(:package4) { create(:npm_package, project: project) }
|
|
|
|
it_behaves_like 'returns paginated packages'
|
|
end
|
|
end
|
|
|
|
context 'with sorting' do
|
|
let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
|
|
|
|
before do
|
|
travel_to(1.day.ago) do
|
|
package3
|
|
end
|
|
end
|
|
|
|
it_behaves_like 'package sorting', 'name' do
|
|
let(:packages) { [package1, package2, package3] }
|
|
end
|
|
|
|
it_behaves_like 'package sorting', 'created_at' do
|
|
let(:packages) { [package3, package1, package2] }
|
|
end
|
|
|
|
it_behaves_like 'package sorting', 'version' do
|
|
let(:packages) { [package3, package2, package1] }
|
|
end
|
|
|
|
it_behaves_like 'package sorting', 'type' do
|
|
let(:packages) { [package3, package1, package2] }
|
|
end
|
|
end
|
|
|
|
it_behaves_like 'filters on each package_type', is_project: true
|
|
|
|
context 'filtering on package_name' do
|
|
include_context 'package filter context'
|
|
|
|
it 'returns the named package' do
|
|
url = package_filter_url(:name, 'nuget')
|
|
get api(url, user)
|
|
|
|
expect(json_response.length).to eq(1)
|
|
expect(json_response.first['name']).to include(package2.name)
|
|
end
|
|
end
|
|
|
|
it_behaves_like 'with versionless packages'
|
|
it_behaves_like 'with status param'
|
|
it_behaves_like 'does not cause n^2 queries'
|
|
end
|
|
end
|
|
|
|
describe 'GET /projects/:id/packages/:package_id' do
|
|
let(:single_package_schema) { 'public_api/v4/packages/package' }
|
|
|
|
subject { get api(package_url, user) }
|
|
|
|
shared_examples 'no destroy url' do
|
|
it 'returns no destroy url' do
|
|
subject
|
|
|
|
expect(json_response['_links']).not_to include('delete_api_path')
|
|
end
|
|
end
|
|
|
|
shared_examples 'destroy url' do
|
|
it 'returns destroy url' do
|
|
subject
|
|
|
|
expect(json_response['_links']['delete_api_path']).to be_present
|
|
end
|
|
end
|
|
|
|
context 'without the need for a license' do
|
|
context 'with build info' do
|
|
it 'does not result in additional queries' do
|
|
control = ActiveRecord::QueryRecorder.new do
|
|
get api(package_url, user)
|
|
end
|
|
|
|
pipeline = create(:ci_pipeline, user: user, project: project)
|
|
create(:ci_build, user: user, pipeline: pipeline, project: project)
|
|
create(:package_build_info, package: package1, pipeline: pipeline)
|
|
|
|
expect do
|
|
get api(package_url, user)
|
|
end.not_to exceed_query_limit(control)
|
|
end
|
|
end
|
|
|
|
context 'project is public' do
|
|
it 'returns 200 and the package information' do
|
|
subject
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(response).to match_response_schema(single_package_schema)
|
|
end
|
|
|
|
it 'returns 404 when the package does not exist' do
|
|
get api(no_package_url, user)
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
|
|
it 'returns 404 for the package from a different project' do
|
|
get api(wrong_package_url, user)
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
|
|
it_behaves_like 'no destroy url'
|
|
|
|
context 'with JOB-TOKEN auth' do
|
|
let(:job) { create(:ci_build, :running, user: user, project: project) }
|
|
|
|
subject { get api(package_url, job_token: job.token) }
|
|
|
|
it_behaves_like 'returns package', :project, :maintainer
|
|
it_behaves_like 'returns package', :project, :developer
|
|
it_behaves_like 'returns package', :project, :reporter
|
|
it_behaves_like 'returns package', :project, :no_type
|
|
it_behaves_like 'returns package', :project, :guest
|
|
end
|
|
|
|
context 'with a package without last_downloaded_at' do
|
|
let(:package_url) { "/projects/#{project.id}/packages/#{package2.id}" }
|
|
|
|
it 'returns 200 and the package information' do
|
|
subject
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(response).to match_response_schema(single_package_schema)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'project is private' do
|
|
let(:project) { create(:project, :private) }
|
|
|
|
it 'returns 404 for non authenticated user' do
|
|
get api(package_url)
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
|
|
it 'returns 404 for a user without access to the project' do
|
|
subject
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
|
|
context 'user is a developer' do
|
|
before do
|
|
project.add_developer(user)
|
|
end
|
|
|
|
it 'returns 200 and the package information' do
|
|
subject
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(response).to match_response_schema(single_package_schema)
|
|
end
|
|
|
|
it_behaves_like 'no destroy url'
|
|
end
|
|
|
|
context 'user is a maintainer' do
|
|
before do
|
|
project.add_maintainer(user)
|
|
end
|
|
|
|
it_behaves_like 'destroy url'
|
|
end
|
|
|
|
context 'with JOB-TOKEN auth' do
|
|
let(:job) { create(:ci_build, :running, user: user, project: project) }
|
|
|
|
subject { get api(package_url, job_token: job.token) }
|
|
|
|
it_behaves_like 'returns package', :project, :maintainer
|
|
it_behaves_like 'returns package', :project, :developer
|
|
it_behaves_like 'returns package', :project, :reporter
|
|
# TODO uncomment when https://gitlab.com/gitlab-org/gitlab/-/issues/370998 is resolved
|
|
# it_behaves_like 'rejects packages access', :project, :guest, :not_found
|
|
it_behaves_like 'rejects packages access', :project, :no_type, :not_found
|
|
end
|
|
|
|
context 'with pipeline' do
|
|
let!(:package1) { create(:npm_package, :with_build, project: project) }
|
|
|
|
it 'returns the pipeline info' do
|
|
project.add_developer(user)
|
|
|
|
get api(package_url, user)
|
|
|
|
expect(response).to have_gitlab_http_status(:ok)
|
|
expect(response).to match_response_schema('public_api/v4/packages/package_with_build')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when package has no default status' do
|
|
let!(:package1) { create(:npm_package, :error, project: project) }
|
|
|
|
it 'returns 404' do
|
|
subject
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'DELETE /projects/:id/packages/:package_id' do
|
|
context 'without the need for a license' do
|
|
context 'project is public' do
|
|
it 'returns 403 for non authenticated user' do
|
|
expect { delete api(package_url) }.not_to change { ::Packages::Package.pending_destruction.count }
|
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
|
end
|
|
|
|
it 'returns 403 for a user without access to the project' do
|
|
expect { delete api(package_url, user) }.not_to change { ::Packages::Package.pending_destruction.count }
|
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
|
end
|
|
end
|
|
|
|
context 'project is private' do
|
|
let(:project) { create(:project, :private) }
|
|
|
|
before do
|
|
expect(::Packages::Maven::Metadata::SyncWorker).not_to receive(:perform_async)
|
|
end
|
|
|
|
it 'returns 404 for non authenticated user' do
|
|
expect { delete api(package_url) }.not_to change { ::Packages::Package.pending_destruction.count }
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
|
|
it 'returns 404 for a user without access to the project' do
|
|
expect { delete api(package_url, user) }.not_to change { ::Packages::Package.pending_destruction.count }
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
|
|
it 'returns 404 when the package does not exist' do
|
|
project.add_maintainer(user)
|
|
|
|
expect { delete api(no_package_url, user) }.not_to change { ::Packages::Package.pending_destruction.count }
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
|
|
it 'returns 404 for the package from a different project' do
|
|
project.add_maintainer(user)
|
|
|
|
expect { delete api(wrong_package_url, user) }.not_to change { ::Packages::Package.pending_destruction.count }
|
|
|
|
expect(response).to have_gitlab_http_status(:not_found)
|
|
end
|
|
|
|
it 'returns 403 for a user without enough permissions' do
|
|
project.add_developer(user)
|
|
|
|
expect { delete api(package_url, user) }.not_to change { ::Packages::Package.pending_destruction.count }
|
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
|
end
|
|
|
|
it 'returns 204' do
|
|
project.add_maintainer(user)
|
|
|
|
expect { delete api(package_url, user) }.to change { ::Packages::Package.pending_destruction.count }.by(1)
|
|
|
|
expect(response).to have_gitlab_http_status(:no_content)
|
|
end
|
|
|
|
context 'with JOB-TOKEN auth' do
|
|
let(:job) { create(:ci_build, :running, user: user, project: project) }
|
|
|
|
it 'returns 403 for a user without enough permissions' do
|
|
project.add_developer(user)
|
|
|
|
expect { delete api(package_url, job_token: job.token) }.not_to change { ::Packages::Package.pending_destruction.count }
|
|
|
|
expect(response).to have_gitlab_http_status(:forbidden)
|
|
end
|
|
|
|
it 'returns 204' do
|
|
project.add_maintainer(user)
|
|
|
|
expect { delete api(package_url, job_token: job.token) }.to change { ::Packages::Package.pending_destruction.count }.by(1)
|
|
|
|
expect(response).to have_gitlab_http_status(:no_content)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with a maven package' do
|
|
let_it_be(:package1) { create(:maven_package, project: project) }
|
|
|
|
it 'enqueues a sync worker job' do
|
|
project.add_maintainer(user)
|
|
|
|
expect(::Packages::Maven::Metadata::SyncWorker)
|
|
.to receive(:perform_async).with(user.id, project.id, package1.name)
|
|
|
|
delete api(package_url, user)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|