2020-07-28 23:09:34 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
|
2023-05-27 22:25:52 +05:30
|
|
|
RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_registry do
|
2022-08-27 11:52:29 +05:30
|
|
|
let(:namespace) { create(:namespace) }
|
2020-07-28 23:09:34 +05:30
|
|
|
let(:project) { create(:project, namespace: namespace) }
|
|
|
|
let(:user) { create(:user) }
|
|
|
|
let(:version) { '1.0.1' }
|
|
|
|
|
|
|
|
let(:params) do
|
|
|
|
Gitlab::Json.parse(fixture_file('packages/npm/payload.json')
|
|
|
|
.gsub('@root/npm-test', package_name)
|
|
|
|
.gsub('1.0.1', version)).with_indifferent_access
|
|
|
|
end
|
2020-10-24 23:57:45 +05:30
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
let(:package_name) { "@#{namespace.path}/my-app" }
|
2021-12-11 22:18:48 +05:30
|
|
|
let(:version_data) { params.dig('versions', '1.0.1') }
|
2020-07-28 23:09:34 +05:30
|
|
|
|
|
|
|
subject { described_class.new(project, user, params).execute }
|
|
|
|
|
|
|
|
shared_examples 'valid package' do
|
|
|
|
it 'creates a package' do
|
|
|
|
expect { subject }
|
|
|
|
.to change { Packages::Package.count }.by(1)
|
|
|
|
.and change { Packages::Package.npm.count }.by(1)
|
|
|
|
.and change { Packages::Tag.count }.by(1)
|
2021-12-11 22:18:48 +05:30
|
|
|
.and change { Packages::Npm::Metadatum.count }.by(1)
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
it_behaves_like 'assigns the package creator' do
|
|
|
|
let(:package) { subject }
|
|
|
|
end
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
it { is_expected.to be_valid }
|
|
|
|
|
|
|
|
it 'creates a package with name and version' do
|
|
|
|
package = subject
|
|
|
|
|
|
|
|
expect(package.name).to eq(package_name)
|
|
|
|
expect(package.version).to eq(version)
|
|
|
|
end
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
it { expect(subject.npm_metadatum.package_json).to eq(version_data) }
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
it { expect(subject.name).to eq(package_name) }
|
|
|
|
it { expect(subject.version).to eq(version) }
|
2021-04-17 20:07:23 +05:30
|
|
|
|
|
|
|
context 'with build info' do
|
|
|
|
let(:job) { create(:ci_build, user: user) }
|
|
|
|
let(:params) { super().merge(build: job) }
|
|
|
|
|
|
|
|
it_behaves_like 'assigns build to package'
|
|
|
|
it_behaves_like 'assigns status to package'
|
|
|
|
|
|
|
|
it 'creates a package file build info' do
|
|
|
|
expect { subject }.to change { Packages::PackageFileBuildInfo.count }.by(1)
|
|
|
|
end
|
|
|
|
end
|
2021-12-11 22:18:48 +05:30
|
|
|
|
|
|
|
context 'with a too large metadata structure' do
|
|
|
|
before do
|
|
|
|
params[:versions][version][:test] = 'test' * 10000
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not create the package' do
|
|
|
|
expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Package json structure is too large')
|
|
|
|
.and not_change { Packages::Package.count }
|
|
|
|
.and not_change { Packages::Package.npm.count }
|
|
|
|
.and not_change { Packages::Tag.count }
|
|
|
|
.and not_change { Packages::Npm::Metadatum.count }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
described_class::PACKAGE_JSON_NOT_ALLOWED_FIELDS.each do |field|
|
|
|
|
context "with not allowed #{field} field" do
|
|
|
|
before do
|
|
|
|
params[:versions][version][field] = 'test'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is persisted without the field' do
|
|
|
|
expect { subject }
|
|
|
|
.to change { Packages::Package.count }.by(1)
|
|
|
|
.and change { Packages::Package.npm.count }.by(1)
|
|
|
|
.and change { Packages::Tag.count }.by(1)
|
|
|
|
.and change { Packages::Npm::Metadatum.count }.by(1)
|
|
|
|
expect(subject.npm_metadatum.package_json[field]).to be_blank
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe '#execute' do
|
|
|
|
context 'scoped package' do
|
|
|
|
it_behaves_like 'valid package'
|
2021-04-17 20:07:23 +05:30
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
context 'scoped package not following the naming convention' do
|
|
|
|
let(:package_name) { '@any-scope/package' }
|
2021-01-29 00:20:46 +05:30
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
it_behaves_like 'valid package'
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
context 'unscoped package' do
|
|
|
|
let(:package_name) { 'unscoped-package' }
|
2020-07-28 23:09:34 +05:30
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
it_behaves_like 'valid package'
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'package already exists' do
|
|
|
|
let(:package_name) { "@#{namespace.path}/my_package" }
|
|
|
|
let!(:existing_package) { create(:npm_package, project: project, name: package_name, version: '1.0.1') }
|
|
|
|
|
|
|
|
it { expect(subject[:http_status]).to eq 403 }
|
|
|
|
it { expect(subject[:message]).to be 'Package already exists.' }
|
2022-03-02 08:16:31 +05:30
|
|
|
|
|
|
|
context 'marked as pending_destruction' do
|
|
|
|
before do
|
|
|
|
existing_package.pending_destruction!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates a new package' do
|
|
|
|
expect { subject }
|
|
|
|
.to change { Packages::Package.count }.by(1)
|
|
|
|
.and change { Packages::Package.npm.count }.by(1)
|
|
|
|
.and change { Packages::Tag.count }.by(1)
|
|
|
|
.and change { Packages::Npm::Metadatum.count }.by(1)
|
|
|
|
end
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
2022-01-12 12:59:36 +05:30
|
|
|
describe 'max file size validation' do
|
2022-08-27 11:52:29 +05:30
|
|
|
let(:max_file_size) { 5.bytes }
|
2022-01-12 12:59:36 +05:30
|
|
|
|
|
|
|
shared_examples_for 'max file size validation failure' do
|
|
|
|
it 'returns a 400 error', :aggregate_failures do
|
|
|
|
expect(subject[:http_status]).to eq 400
|
|
|
|
expect(subject[:message]).to be 'File is too large.'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
before do
|
2022-01-12 12:59:36 +05:30
|
|
|
project.actual_limits.update!(npm_max_file_size: max_file_size)
|
2020-11-24 15:15:51 +05:30
|
|
|
end
|
|
|
|
|
2022-01-12 12:59:36 +05:30
|
|
|
context 'when max file size is exceeded' do
|
|
|
|
# NOTE: The base64 encoded package data in the fixture file is the "hello\n" string, whose byte size is 6.
|
|
|
|
it_behaves_like 'max file size validation failure'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when file size is faked by setting the attachment length param to a lower size' do
|
2023-01-13 00:05:48 +05:30
|
|
|
let(:params) { super().deep_merge!({ _attachments: { "#{package_name}-#{version}.tgz" => { data: encoded_package_data, length: 1 } } }) }
|
2022-01-12 12:59:36 +05:30
|
|
|
|
|
|
|
# TODO (technical debt): Extract the package size calculation outside the service and add separate specs for it.
|
|
|
|
# Right now we have several contexts here to test the calculation's different scenarios.
|
|
|
|
context "when encoded package data is not padded" do
|
|
|
|
# 'Hello!' (size = 6 bytes) => 'SGVsbG8h'
|
|
|
|
let(:encoded_package_data) { 'SGVsbG8h' }
|
|
|
|
|
|
|
|
it_behaves_like 'max file size validation failure'
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when encoded package data is padded with '='" do
|
2022-08-27 11:52:29 +05:30
|
|
|
let(:max_file_size) { 4.bytes }
|
2022-01-12 12:59:36 +05:30
|
|
|
# 'Hello' (size = 5 bytes) => 'SGVsbG8='
|
|
|
|
let(:encoded_package_data) { 'SGVsbG8=' }
|
|
|
|
|
|
|
|
it_behaves_like 'max file size validation failure'
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when encoded package data is padded with '=='" do
|
2022-08-27 11:52:29 +05:30
|
|
|
let(:max_file_size) { 3.bytes }
|
2022-01-12 12:59:36 +05:30
|
|
|
# 'Hell' (size = 4 bytes) => 'SGVsbA=='
|
|
|
|
let(:encoded_package_data) { 'SGVsbA==' }
|
|
|
|
|
|
|
|
it_behaves_like 'max file size validation failure'
|
|
|
|
end
|
|
|
|
end
|
2020-11-24 15:15:51 +05:30
|
|
|
end
|
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
[
|
|
|
|
'@inv@lid_scope/package',
|
|
|
|
'@scope/sub/group',
|
|
|
|
'@scope/../../package',
|
|
|
|
'@scope%2e%2e%2fpackage'
|
|
|
|
].each do |invalid_package_name|
|
|
|
|
context "with invalid name #{invalid_package_name}" do
|
|
|
|
let(:package_name) { invalid_package_name }
|
|
|
|
|
|
|
|
it 'raises a RecordInvalid error' do
|
|
|
|
expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with empty versions' do
|
2023-01-13 00:05:48 +05:30
|
|
|
let(:params) { super().merge!({ versions: {} }) }
|
2020-07-28 23:09:34 +05:30
|
|
|
|
|
|
|
it { expect(subject[:http_status]).to eq 400 }
|
|
|
|
it { expect(subject[:message]).to eq 'Version is empty.' }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with invalid versions' do
|
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
|
|
|
where(:version) do
|
|
|
|
[
|
|
|
|
'1',
|
|
|
|
'1.2',
|
|
|
|
'1./2.3',
|
|
|
|
'../../../../../1.2.3',
|
|
|
|
'%2e%2e%2f1.2.3'
|
|
|
|
]
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid') }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|