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/models/packages
parent18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff)
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'spec/models/packages')
-rw-r--r--spec/models/packages/composer/metadatum_spec.rb14
-rw-r--r--spec/models/packages/conan/file_metadatum_spec.rb106
-rw-r--r--spec/models/packages/conan/metadatum_spec.rb90
-rw-r--r--spec/models/packages/dependency_link_spec.rb56
-rw-r--r--spec/models/packages/dependency_spec.rb113
-rw-r--r--spec/models/packages/go/module_spec.rb59
-rw-r--r--spec/models/packages/go/module_version_spec.rb114
-rw-r--r--spec/models/packages/maven/metadatum_spec.rb40
-rw-r--r--spec/models/packages/nuget/dependency_link_metadatum_spec.rb32
-rw-r--r--spec/models/packages/nuget/metadatum_spec.rb44
-rw-r--r--spec/models/packages/package_file_spec.rb69
-rw-r--r--spec/models/packages/package_spec.rb485
-rw-r--r--spec/models/packages/pypi/metadatum_spec.rb22
-rw-r--r--spec/models/packages/sem_ver_spec.rb42
-rw-r--r--spec/models/packages/tag_spec.rb62
15 files changed, 1348 insertions, 0 deletions
diff --git a/spec/models/packages/composer/metadatum_spec.rb b/spec/models/packages/composer/metadatum_spec.rb
new file mode 100644
index 00000000000..ae53532696b
--- /dev/null
+++ b/spec/models/packages/composer/metadatum_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Composer::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:target_sha) }
+ it { is_expected.to validate_presence_of(:composer_json) }
+ end
+end
diff --git a/spec/models/packages/conan/file_metadatum_spec.rb b/spec/models/packages/conan/file_metadatum_spec.rb
new file mode 100644
index 00000000000..a66a2813196
--- /dev/null
+++ b/spec/models/packages/conan/file_metadatum_spec.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::FileMetadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package_file) }
+ end
+
+ describe 'validations' do
+ let(:package_file) { create(:conan_package_file, :conan_recipe_file) }
+
+ it { is_expected.to validate_presence_of(:package_file) }
+ it { is_expected.to validate_presence_of(:recipe_revision) }
+
+ describe '#recipe_revision' do
+ it { is_expected.to allow_value("0").for(:recipe_revision) }
+ it { is_expected.not_to allow_value(nil).for(:recipe_revision) }
+ end
+
+ describe '#package_revision_for_package_file' do
+ context 'recipe file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :recipe_file, package_file: package_file) }
+
+ it 'is valid with empty value' do
+ conan_file_metadatum.package_revision = nil
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with value' do
+ conan_file_metadatum.package_revision = '0'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+
+ context 'package file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :package_file, package_file: package_file) }
+
+ it 'is valid with default value' do
+ conan_file_metadatum.package_revision = '0'
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with non-default value' do
+ conan_file_metadatum.package_revision = 'foo'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+ end
+
+ describe '#conan_package_reference_for_package_file' do
+ context 'recipe file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :recipe_file, package_file: package_file) }
+
+ it 'is valid with empty value' do
+ conan_file_metadatum.conan_package_reference = nil
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with value' do
+ conan_file_metadatum.conan_package_reference = '123456789'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+
+ context 'package file' do
+ let(:conan_file_metadatum) { build(:conan_file_metadatum, :package_file, package_file: package_file) }
+
+ it 'is valid with acceptable value' do
+ conan_file_metadatum.conan_package_reference = '123456asdf'
+
+ expect(conan_file_metadatum).to be_valid
+ end
+
+ it 'is invalid with invalid value' do
+ conan_file_metadatum.conan_package_reference = 'foo@bar'
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+
+ it 'is invalid when nil' do
+ conan_file_metadatum.conan_package_reference = nil
+
+ expect(conan_file_metadatum).to be_invalid
+ end
+ end
+ end
+
+ describe '#conan_package_type' do
+ it 'validates package of type conan' do
+ package = build('package')
+ package_file = build('package_file', package: package)
+ conan_file_metadatum = build('conan_file_metadatum', package_file: package_file)
+
+ expect(conan_file_metadatum).not_to be_valid
+ expect(conan_file_metadatum.errors.to_a).to contain_exactly('Package type must be Conan')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/conan/metadatum_spec.rb b/spec/models/packages/conan/metadatum_spec.rb
new file mode 100644
index 00000000000..112f395818b
--- /dev/null
+++ b/spec/models/packages/conan/metadatum_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ let(:fifty_one_characters) { 'f_a' * 17}
+
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:package_username) }
+ it { is_expected.to validate_presence_of(:package_channel) }
+
+ describe '#package_username' do
+ it { is_expected.to allow_value("my-package+username").for(:package_username) }
+ it { is_expected.to allow_value("my_package.username").for(:package_username) }
+ it { is_expected.to allow_value("_my-package.username123").for(:package_username) }
+ it { is_expected.to allow_value("my").for(:package_username) }
+ it { is_expected.not_to allow_value('+my_package').for(:package_username) }
+ it { is_expected.not_to allow_value('.my_package').for(:package_username) }
+ it { is_expected.not_to allow_value('-my_package').for(:package_username) }
+ it { is_expected.not_to allow_value('m').for(:package_username) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:package_username) }
+ it { is_expected.not_to allow_value("my/package").for(:package_username) }
+ it { is_expected.not_to allow_value("my(package)").for(:package_username) }
+ it { is_expected.not_to allow_value("my@package").for(:package_username) }
+ end
+
+ describe '#package_channel' do
+ it { is_expected.to allow_value("beta").for(:package_channel) }
+ it { is_expected.to allow_value("stable+1.0").for(:package_channel) }
+ it { is_expected.to allow_value("my").for(:package_channel) }
+ it { is_expected.to allow_value("my_channel.beta").for(:package_channel) }
+ it { is_expected.to allow_value("_my-channel.beta123").for(:package_channel) }
+ it { is_expected.not_to allow_value('+my_channel').for(:package_channel) }
+ it { is_expected.not_to allow_value('.my_channel').for(:package_channel) }
+ it { is_expected.not_to allow_value('-my_channel').for(:package_channel) }
+ it { is_expected.not_to allow_value('m').for(:package_channel) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:package_channel) }
+ it { is_expected.not_to allow_value("my/channel").for(:package_channel) }
+ it { is_expected.not_to allow_value("my(channel)").for(:package_channel) }
+ it { is_expected.not_to allow_value("my@channel").for(:package_channel) }
+ end
+
+ describe '#conan_package_type' do
+ it 'will not allow a package with a different package_type' do
+ package = build('package')
+ conan_metadatum = build('conan_metadatum', package: package)
+
+ expect(conan_metadatum).not_to be_valid
+ expect(conan_metadatum.errors.to_a).to include('Package type must be Conan')
+ end
+ end
+ end
+
+ describe '#recipe' do
+ let(:package) { create(:conan_package) }
+
+ it 'returns the recipe' do
+ expect(package.conan_recipe).to eq("#{package.name}/#{package.version}@#{package.conan_metadatum.package_username}/#{package.conan_metadatum.package_channel}")
+ end
+ end
+
+ describe '#recipe_url' do
+ let(:package) { create(:conan_package) }
+
+ it 'returns the recipe url' do
+ expect(package.conan_recipe_path).to eq("#{package.name}/#{package.version}/#{package.conan_metadatum.package_username}/#{package.conan_metadatum.package_channel}")
+ end
+ end
+
+ describe '.package_username_from' do
+ let(:full_path) { 'foo/bar/baz-buz' }
+
+ it 'returns the username formatted package path' do
+ expect(described_class.package_username_from(full_path: full_path)).to eq('foo+bar+baz-buz')
+ end
+ end
+
+ describe '.full_path_from' do
+ let(:username) { 'foo+bar+baz-buz' }
+
+ it 'returns the username formatted package path' do
+ expect(described_class.full_path_from(package_username: username)).to eq('foo/bar/baz-buz')
+ end
+ end
+end
diff --git a/spec/models/packages/dependency_link_spec.rb b/spec/models/packages/dependency_link_spec.rb
new file mode 100644
index 00000000000..d8fde8f5eb3
--- /dev/null
+++ b/spec/models/packages/dependency_link_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::DependencyLink, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:dependency_links) }
+ it { is_expected.to belong_to(:dependency).inverse_of(:dependency_links) }
+ it { is_expected.to have_one(:nuget_metadatum).inverse_of(:dependency_link) }
+ end
+
+ describe 'validations' do
+ subject { create(:packages_dependency_link) }
+
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:dependency) }
+
+ context 'package_id and package_dependency_id uniqueness for dependency_type' do
+ it 'is not valid' do
+ exisiting_link = subject
+ link = build(
+ :packages_dependency_link,
+ package: exisiting_link.package,
+ dependency: exisiting_link.dependency,
+ dependency_type: exisiting_link.dependency_type
+ )
+
+ expect(link).not_to be_valid
+ expect(link.errors.to_a).to include("Dependency type has already been taken")
+ end
+ end
+ end
+
+ context 'with multiple links' do
+ let_it_be(:link1) { create(:packages_dependency_link) }
+ let_it_be(:link2) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :devDependencies) }
+ let_it_be(:link3) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :bundleDependencies) }
+
+ subject { described_class }
+
+ describe '.with_dependency_type' do
+ it 'returns links of the given type' do
+ expect(subject.with_dependency_type(:bundleDependencies)).to eq([link3])
+ end
+ end
+
+ describe '.for_package' do
+ let_it_be(:link1) { create(:packages_dependency_link) }
+ let_it_be(:link2) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :devDependencies) }
+ let_it_be(:link3) { create(:packages_dependency_link, dependency: link1.dependency, dependency_type: :bundleDependencies) }
+
+ it 'returns the link for the given package' do
+ expect(subject.for_package(link1.package)).to eq([link1])
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/dependency_spec.rb b/spec/models/packages/dependency_spec.rb
new file mode 100644
index 00000000000..fa6b0fd1848
--- /dev/null
+++ b/spec/models/packages/dependency_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Dependency, type: :model do
+ describe 'relationships' do
+ it { is_expected.to have_many(:dependency_links) }
+ end
+
+ describe 'validations' do
+ subject { create(:packages_dependency) }
+
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_presence_of(:version_pattern) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to(:version_pattern) }
+ end
+
+ describe '.ids_for_package_names_and_version_patterns' do
+ let_it_be(:package_dependency1) { create(:packages_dependency, name: 'foo', version_pattern: '~1.0.0') }
+ let_it_be(:package_dependency2) { create(:packages_dependency, name: 'bar', version_pattern: '~2.5.0') }
+ let_it_be(:expected_ids) { [package_dependency1.id, package_dependency2.id] }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2) }
+ let(:chunk_size) { 50 }
+ let(:rows_limit) { 50 }
+
+ subject { Packages::Dependency.ids_for_package_names_and_version_patterns(names_and_version_patterns, chunk_size, rows_limit) }
+
+ it { is_expected.to match_array(expected_ids) }
+
+ context 'with unknown names' do
+ let(:names_and_version_patterns) { { unknown: '~1.0.0' } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with unknown version patterns' do
+ let(:names_and_version_patterns) { { 'foo' => '~1.0.0beta' } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with a name bigger than column size' do
+ let_it_be(:big_name) { 'a' * (Packages::Dependency::MAX_STRING_LENGTH + 1) }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2).merge(big_name => '~1.0.0') }
+
+ it { is_expected.to match_array(expected_ids) }
+ end
+
+ context 'with a version pattern bigger than column size' do
+ let_it_be(:big_version_pattern) { 'a' * (Packages::Dependency::MAX_STRING_LENGTH + 1) }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2).merge('test' => big_version_pattern) }
+
+ it { is_expected.to match_array(expected_ids) }
+ end
+
+ context 'with too big parameter' do
+ let(:size) { (Packages::Dependency::MAX_CHUNKED_QUERIES_COUNT * chunk_size) + 1 }
+ let(:names_and_version_patterns) { Hash[(1..size).map { |v| [v, v] }] }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'Too many names_and_version_patterns') }
+ end
+
+ context 'with parameters size' do
+ let_it_be(:package_dependency3) { create(:packages_dependency, name: 'foo3', version_pattern: '~1.5.3') }
+ let_it_be(:package_dependency4) { create(:packages_dependency, name: 'foo4', version_pattern: '~1.5.4') }
+ let_it_be(:package_dependency5) { create(:packages_dependency, name: 'foo5', version_pattern: '~1.5.5') }
+ let_it_be(:package_dependency6) { create(:packages_dependency, name: 'foo6', version_pattern: '~1.5.6') }
+ let_it_be(:package_dependency7) { create(:packages_dependency, name: 'foo7', version_pattern: '~1.5.7') }
+ let(:expected_ids) { [package_dependency1.id, package_dependency2.id, package_dependency3.id, package_dependency4.id, package_dependency5.id, package_dependency6.id, package_dependency7.id] }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2, package_dependency3, package_dependency4, package_dependency5, package_dependency6, package_dependency7) }
+
+ context 'above the chunk size' do
+ let(:chunk_size) { 2 }
+
+ it { is_expected.to match_array(expected_ids) }
+ end
+
+ context 'selecting too many rows' do
+ let(:rows_limit) { 2 }
+
+ it { expect { subject }.to raise_error(ArgumentError, 'Too many Dependencies selected') }
+ end
+ end
+ end
+
+ describe '.for_package_names_and_version_patterns' do
+ let_it_be(:package_dependency1) { create(:packages_dependency, name: 'foo', version_pattern: '~1.0.0') }
+ let_it_be(:package_dependency2) { create(:packages_dependency, name: 'bar', version_pattern: '~2.5.0') }
+ let_it_be(:expected_array) { [package_dependency1, package_dependency2] }
+ let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2) }
+
+ subject { Packages::Dependency.for_package_names_and_version_patterns(names_and_version_patterns) }
+
+ it { is_expected.to match_array(expected_array) }
+
+ context 'with unknown names' do
+ let(:names_and_version_patterns) { { unknown: '~1.0.0' } }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with unknown version patterns' do
+ let(:names_and_version_patterns) { { 'foo' => '~1.0.0beta' } }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ def build_names_and_version_patterns(*package_dependencies)
+ result = Hash.new { |h, dependency| h[dependency.name] = dependency.version_pattern }
+ package_dependencies.each { |dependency| result[dependency] }
+ result
+ end
+end
diff --git a/spec/models/packages/go/module_spec.rb b/spec/models/packages/go/module_spec.rb
new file mode 100644
index 00000000000..03af4cf4b70
--- /dev/null
+++ b/spec/models/packages/go/module_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::Module, type: :model do
+ before do
+ stub_feature_flags(go_proxy_disable_gomod_validation: false)
+ end
+
+ describe '#path_valid?' do
+ context 'with root path' do
+ let_it_be(:package) { create(:go_module) }
+
+ context 'with major version 0' do
+ it('returns true') { expect(package.path_valid?(0)).to eq(true) }
+ end
+
+ context 'with major version 1' do
+ it('returns true') { expect(package.path_valid?(1)).to eq(true) }
+ end
+
+ context 'with major version 2' do
+ it('returns false') { expect(package.path_valid?(2)).to eq(false) }
+ end
+ end
+
+ context 'with path ./v2' do
+ let_it_be(:package) { create(:go_module, path: '/v2') }
+
+ context 'with major version 0' do
+ it('returns false') { expect(package.path_valid?(0)).to eq(false) }
+ end
+
+ context 'with major version 1' do
+ it('returns false') { expect(package.path_valid?(1)).to eq(false) }
+ end
+
+ context 'with major version 2' do
+ it('returns true') { expect(package.path_valid?(2)).to eq(true) }
+ end
+ end
+ end
+
+ describe '#gomod_valid?' do
+ let_it_be(:package) { create(:go_module) }
+
+ context 'with good gomod' do
+ it('returns true') { expect(package.gomod_valid?("module #{package.name}")).to eq(true) }
+ end
+
+ context 'with bad gomod' do
+ it('returns false') { expect(package.gomod_valid?("module #{package.name}/v2")).to eq(false) }
+ end
+
+ context 'with empty gomod' do
+ it('returns false') { expect(package.gomod_valid?("")).to eq(false) }
+ end
+ end
+end
diff --git a/spec/models/packages/go/module_version_spec.rb b/spec/models/packages/go/module_version_spec.rb
new file mode 100644
index 00000000000..c4c6a07d9e9
--- /dev/null
+++ b/spec/models/packages/go/module_version_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Go::ModuleVersion, type: :model do
+ let_it_be(:user) { create :user }
+ let_it_be(:project) { create :project_empty_repo, creator: user, path: 'my-go-lib' }
+ let_it_be(:mod) { create :go_module, project: project }
+
+ before :all do
+ create :go_module_commit, :files, project: project, tag: 'v1.0.0', files: { 'README.md' => 'Hi' }
+ create :go_module_commit, :module, project: project, tag: 'v1.0.1'
+ create :go_module_commit, :package, project: project, tag: 'v1.0.2', path: 'pkg'
+ create :go_module_commit, :module, project: project, tag: 'v1.0.3', name: 'mod'
+ create :go_module_commit, :files, project: project, files: { 'y.go' => "package a\n" }
+ create :go_module_commit, :module, project: project, name: 'v2'
+ create :go_module_commit, :files, project: project, tag: 'v2.0.0', files: { 'v2/x.go' => "package a\n" }
+ end
+
+ shared_examples '#files' do |desc, *entries|
+ it "returns #{desc}" do
+ actual = version.files.map { |x| x }.to_set
+ expect(actual).to eq(entries.to_set)
+ end
+ end
+
+ shared_examples '#archive' do |desc, *entries|
+ it "returns an archive of #{desc}" do
+ expected = entries.map { |e| "#{version.full_name}/#{e}" }.to_set
+
+ actual = Set[]
+ Zip::InputStream.open(StringIO.new(version.archive.string)) do |zip|
+ while (entry = zip.get_next_entry)
+ actual.add(entry.name)
+ end
+ end
+
+ expect(actual).to eq(expected)
+ end
+ end
+
+ describe '#name' do
+ context 'with ref and name specified' do
+ let_it_be(:version) { create :go_module_version, mod: mod, name: 'foobar', commit: project.repository.head_commit, ref: project.repository.find_tag('v1.0.0') }
+ it('returns that name') { expect(version.name).to eq('foobar') }
+ end
+
+ context 'with ref specified and name unspecified' do
+ let_it_be(:version) { create :go_module_version, mod: mod, commit: project.repository.head_commit, ref: project.repository.find_tag('v1.0.0') }
+ it('returns the name of the ref') { expect(version.name).to eq('v1.0.0') }
+ end
+
+ context 'with ref and name unspecified' do
+ let_it_be(:version) { create :go_module_version, mod: mod, commit: project.repository.head_commit }
+ it('returns nil') { expect(version.name).to eq(nil) }
+ end
+ end
+
+ describe '#gomod' do
+ context 'with go.mod missing' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.0' }
+ it('returns nil') { expect(version.gomod).to eq(nil) }
+ end
+
+ context 'with go.mod present' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.1' }
+ it('returns the contents of go.mod') { expect(version.gomod).to eq("module #{mod.name}\n") }
+ end
+ end
+
+ describe '#files' do
+ context 'with a root module' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.2' }
+ it_behaves_like '#files', 'all the files', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+ end
+
+ context 'with a root module and a submodule' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#files', 'files excluding the submodule', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+
+ context 'with the submodule\'s path' do
+ let_it_be(:mod) { create :go_module, project: project, path: 'mod' }
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#files', 'the submodule\'s files', 'mod/go.mod', 'mod/a.go'
+ end
+ end
+ end
+
+ describe '#archive' do
+ context 'with a root module' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.2' }
+ it_behaves_like '#archive', 'all the files', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+ end
+
+ context 'with a root module and a submodule' do
+ context 'with an empty module path' do
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#archive', 'files excluding the submodule', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
+ end
+
+ context 'with the submodule\'s path' do
+ let_it_be(:mod) { create :go_module, project: project, path: 'mod' }
+ let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
+ it_behaves_like '#archive', 'the submodule\'s files', 'go.mod', 'a.go'
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/maven/metadatum_spec.rb b/spec/models/packages/maven/metadatum_spec.rb
new file mode 100644
index 00000000000..16f6929d710
--- /dev/null
+++ b/spec/models/packages/maven/metadatum_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Maven::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+
+ describe '#app_name' do
+ it { is_expected.to allow_value("my-app").for(:app_name) }
+ it { is_expected.not_to allow_value("my/app").for(:app_name) }
+ it { is_expected.not_to allow_value("my(app)").for(:app_name) }
+ end
+
+ describe '#app_group' do
+ it { is_expected.to allow_value("my.domain.com").for(:app_group) }
+ it { is_expected.not_to allow_value("my/domain/com").for(:app_group) }
+ it { is_expected.not_to allow_value("my(domain)").for(:app_group) }
+ end
+
+ describe '#path' do
+ it { is_expected.to allow_value("my/domain/com/my-app").for(:path) }
+ it { is_expected.to allow_value("my/domain/com/my-app/1.0-SNAPSHOT").for(:path) }
+ it { is_expected.not_to allow_value("my(domain)com.my-app").for(:path) }
+ end
+
+ describe '#maven_package_type' do
+ it 'will not allow a package with a different package_type' do
+ package = build('conan_package')
+ maven_metadatum = build('maven_metadatum', package: package)
+
+ expect(maven_metadatum).not_to be_valid
+ expect(maven_metadatum.errors.to_a).to include('Package type must be Maven')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/nuget/dependency_link_metadatum_spec.rb b/spec/models/packages/nuget/dependency_link_metadatum_spec.rb
new file mode 100644
index 00000000000..0c03c65028e
--- /dev/null
+++ b/spec/models/packages/nuget/dependency_link_metadatum_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::DependencyLinkMetadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:dependency_link) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:dependency_link) }
+ it { is_expected.to validate_presence_of(:target_framework) }
+
+ describe '#ensure_nuget_package_type' do
+ it 'validates package of type nuget' do
+ package = build('conan_package')
+ dependency_link = build('packages_dependency_link', package: package)
+ nuget_metadatum = build('nuget_dependency_link_metadatum', dependency_link: dependency_link)
+
+ expect(nuget_metadatum).not_to be_valid
+ expect(nuget_metadatum.errors.to_a).to contain_exactly('Package type must be NuGet')
+ end
+
+ it 'validates package of type nuget with nil dependency_link' do
+ nuget_metadatum = build('nuget_dependency_link_metadatum', dependency_link: nil)
+
+ expect(nuget_metadatum).not_to be_valid
+ expect(nuget_metadatum.errors.to_a).to contain_exactly("Dependency link can't be blank", 'Package type must be NuGet')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/nuget/metadatum_spec.rb b/spec/models/packages/nuget/metadatum_spec.rb
new file mode 100644
index 00000000000..c1bc5429500
--- /dev/null
+++ b/spec/models/packages/nuget/metadatum_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Nuget::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:nuget_metadatum) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+
+ %i[license_url project_url icon_url].each do |url|
+ describe "##{url}" do
+ it { is_expected.to allow_value('http://sandbox.com').for(url) }
+ it { is_expected.to allow_value('https://sandbox.com').for(url) }
+ it { is_expected.not_to allow_value('123').for(url) }
+ it { is_expected.not_to allow_value('sandbox.com').for(url) }
+ end
+
+ describe '#ensure_at_least_one_field_supplied' do
+ subject { build(:nuget_metadatum) }
+
+ it 'rejects unfilled metadatum' do
+ subject.attributes = { license_url: nil, project_url: nil, icon_url: nil }
+
+ expect(subject).not_to be_valid
+ expect(subject.errors).to contain_exactly('Nuget metadatum must have at least license_url, project_url or icon_url set')
+ end
+ end
+
+ describe '#ensure_nuget_package_type' do
+ subject { build(:nuget_metadatum) }
+
+ it 'rejects if not linked to a nuget package' do
+ subject.package = build(:npm_package)
+
+ expect(subject).not_to be_valid
+ expect(subject.errors).to contain_exactly('Package type must be NuGet')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb
new file mode 100644
index 00000000000..7758ed4a500
--- /dev/null
+++ b/spec/models/packages/package_file_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::PackageFile, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ it { is_expected.to have_one(:conan_file_metadatum) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+ end
+
+ context 'with package filenames' do
+ let_it_be(:package_file1) { create(:package_file, :xml, file_name: 'FooBar') }
+ let_it_be(:package_file2) { create(:package_file, :xml, file_name: 'ThisIsATest') }
+
+ describe '.with_file_name' do
+ let(:filename) { 'FooBar' }
+
+ subject { described_class.with_file_name(filename) }
+
+ it { is_expected.to match_array([package_file1]) }
+ end
+
+ describe '.with_file_name_like' do
+ let(:filename) { 'foobar' }
+
+ subject { described_class.with_file_name_like(filename) }
+
+ it { is_expected.to match_array([package_file1]) }
+ end
+ end
+
+ it_behaves_like 'UpdateProjectStatistics' do
+ subject { build(:package_file, :jar, size: 42) }
+
+ before do
+ allow_any_instance_of(Packages::PackageFileUploader).to receive(:size).and_return(42)
+ end
+ end
+
+ describe '.with_conan_package_reference' do
+ let_it_be(:non_matching_package_file) { create(:package_file, :nuget) }
+ let_it_be(:metadatum) { create(:conan_file_metadatum, :package_file) }
+ let_it_be(:reference) { metadatum.conan_package_reference}
+
+ it 'returns matching packages' do
+ expect(described_class.with_conan_package_reference(reference))
+ .to eq([metadatum.package_file])
+ end
+ end
+
+ describe '#update_file_metadata callback' do
+ let_it_be(:package_file) { build(:package_file, :nuget, file_store: nil, size: nil) }
+
+ subject { package_file.save! }
+
+ it 'updates metadata columns' do
+ expect(package_file)
+ .to receive(:update_file_metadata)
+ .and_call_original
+
+ expect { subject }
+ .to change { package_file.file_store }.from(nil).to(::Packages::PackageFileUploader::Store::LOCAL)
+ .and change { package_file.size }.from(nil).to(3513)
+ end
+ end
+end
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
new file mode 100644
index 00000000000..4170bf595f0
--- /dev/null
+++ b/spec/models/packages/package_spec.rb
@@ -0,0 +1,485 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Package, type: :model do
+ include SortingHelper
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:package_files).dependent(:destroy) }
+ it { is_expected.to have_many(:dependency_links).inverse_of(:package) }
+ it { is_expected.to have_many(:tags).inverse_of(:package) }
+ it { is_expected.to have_one(:conan_metadatum).inverse_of(:package) }
+ it { is_expected.to have_one(:maven_metadatum).inverse_of(:package) }
+ it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) }
+ end
+
+ describe '.with_composer_target' do
+ let!(:package1) { create(:composer_package, :with_metadatum, sha: '123') }
+ let!(:package2) { create(:composer_package, :with_metadatum, sha: '123') }
+ let!(:package3) { create(:composer_package, :with_metadatum, sha: '234') }
+
+ subject { described_class.with_composer_target('123').to_a }
+
+ it 'selects packages with the specified sha' do
+ expect(subject).to include(package1)
+ expect(subject).to include(package2)
+ expect(subject).not_to include(package3)
+ end
+ end
+
+ describe '.sort_by_attribute' do
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project, :public, namespace: group, name: 'project A') }
+ let!(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
+ let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
+ let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
+
+ before do
+ travel_to(1.day.ago) do
+ package3
+ end
+ end
+
+ RSpec.shared_examples 'package sorting by attribute' do |order_by|
+ subject { described_class.where(id: packages.map(&:id)).sort_by_attribute("#{order_by}_#{sort}").to_a }
+
+ context "sorting by #{order_by}" do
+ context 'ascending order' do
+ let(:sort) { 'asc' }
+
+ it { is_expected.to eq packages }
+ end
+
+ context 'descending order' do
+ let(:sort) { 'desc' }
+
+ it { is_expected.to eq packages.reverse }
+ end
+ end
+ end
+
+ it_behaves_like 'package sorting by attribute', 'name' do
+ let(:packages) { [package1, package2, package3] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'created_at' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'version' do
+ let(:packages) { [package3, package2, package1] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'type' do
+ let(:packages) { [package3, package1, package2] }
+ end
+
+ it_behaves_like 'package sorting by attribute', 'project_path' do
+ let(:another_project) { create(:project, :public, namespace: group, name: 'project B') }
+ let!(:package4) { create(:npm_package, project: another_project, version: '3.1.0', name: "@#{project.root_namespace.path}/bar") }
+
+ let(:packages) { [package1, package2, package3, package4] }
+ end
+ end
+
+ describe 'validations' do
+ subject { create(:package) }
+
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id, :version, :package_type) }
+
+ describe '#name' do
+ it { is_expected.to allow_value("my/domain/com/my-app").for(:name) }
+ it { is_expected.to allow_value("my.app-11.07.2018").for(:name) }
+ it { is_expected.not_to allow_value("my(dom$$$ain)com.my-app").for(:name) }
+
+ context 'conan package' do
+ subject { create(:conan_package) }
+
+ let(:fifty_one_characters) {'f_b' * 17}
+
+ it { is_expected.to allow_value('foo+bar').for(:name) }
+ it { is_expected.to allow_value('foo_bar').for(:name) }
+ it { is_expected.to allow_value('foo.bar').for(:name) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:name) }
+ it { is_expected.not_to allow_value('+foobar').for(:name) }
+ it { is_expected.not_to allow_value('.foobar').for(:name) }
+ it { is_expected.not_to allow_value('%foo%bar').for(:name) }
+ end
+ end
+
+ describe '#version' do
+ RSpec.shared_examples 'validating version to be SemVer compliant for' do |factory_name|
+ context "for #{factory_name}" do
+ subject { create(factory_name) }
+
+ it { is_expected.to allow_value('1.2.3').for(:version) }
+ it { is_expected.to allow_value('1.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('1.2.3-alpha.3').for(:version) }
+ it { is_expected.not_to allow_value('1').for(:version) }
+ it { is_expected.not_to allow_value('1.2').for(:version) }
+ it { is_expected.not_to allow_value('1./2.3').for(:version) }
+ it { is_expected.not_to allow_value('../../../../../1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ end
+ end
+
+ context 'conan package' do
+ subject { create(:conan_package) }
+
+ let(:fifty_one_characters) {'1.2' * 17}
+
+ it { is_expected.to allow_value('1.2').for(:version) }
+ it { is_expected.to allow_value('1.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('1.2.3-pre1+build2').for(:version) }
+ it { is_expected.not_to allow_value('1').for(:version) }
+ it { is_expected.not_to allow_value(fifty_one_characters).for(:version) }
+ it { is_expected.not_to allow_value('1./2.3').for(:version) }
+ it { is_expected.not_to allow_value('.1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('+1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ end
+
+ context 'maven package' do
+ subject { create(:maven_package) }
+
+ it { is_expected.to allow_value('0').for(:version) }
+ it { is_expected.to allow_value('1').for(:version) }
+ it { is_expected.to allow_value('10').for(:version) }
+ it { is_expected.to allow_value('1.0').for(:version) }
+ it { is_expected.to allow_value('1.3.350.v20200505-1744').for(:version) }
+ it { is_expected.to allow_value('1.1-beta-2').for(:version) }
+ it { is_expected.to allow_value('1.2-SNAPSHOT').for(:version) }
+ it { is_expected.to allow_value('12.1.2-2-1').for(:version) }
+ it { is_expected.to allow_value('1.2.3..beta').for(:version) }
+ it { is_expected.to allow_value('1.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('10.2.3-beta').for(:version) }
+ it { is_expected.to allow_value('2.0.0.v200706041905-7C78EK9E_EkMNfNOd2d8qq').for(:version) }
+ it { is_expected.to allow_value('1.2-alpha-1-20050205.060708-1').for(:version) }
+ it { is_expected.to allow_value('703220b4e2cea9592caeb9f3013f6b1e5335c293').for(:version) }
+ it { is_expected.to allow_value('RELEASE').for(:version) }
+ it { is_expected.not_to allow_value('..1.2.3').for(:version) }
+ it { is_expected.not_to allow_value(' 1.2.3').for(:version) }
+ it { is_expected.not_to allow_value("1.2.3 \r\t").for(:version) }
+ it { is_expected.not_to allow_value("\r\t 1.2.3").for(:version) }
+ it { is_expected.not_to allow_value('1.2.3-4/../../').for(:version) }
+ it { is_expected.not_to allow_value('1.2.3-4%2e%2e%').for(:version) }
+ it { is_expected.not_to allow_value('../../../../../1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ end
+
+ it_behaves_like 'validating version to be SemVer compliant for', :npm_package
+ it_behaves_like 'validating version to be SemVer compliant for', :nuget_package
+ end
+
+ describe '#package_already_taken' do
+ context 'npm package' do
+ let!(:package) { create(:npm_package) }
+
+ it 'will not allow a package of the same name' do
+ new_package = build(:npm_package, name: package.name)
+
+ expect(new_package).not_to be_valid
+ end
+ end
+
+ context 'maven package' do
+ let!(:package) { create(:maven_package) }
+
+ it 'will allow a package of the same name' do
+ new_package = build(:maven_package, name: package.name)
+
+ expect(new_package).to be_valid
+ end
+ end
+ end
+
+ context "recipe uniqueness for conan packages" do
+ let!(:package) { create('conan_package') }
+
+ it "will allow a conan package with same project, name, version and package_type" do
+ new_package = build('conan_package', project: package.project, name: package.name, version: package.version)
+ new_package.conan_metadatum.package_channel = 'beta'
+ expect(new_package).to be_valid
+ end
+
+ it "will not allow a conan package with same recipe (name, version, metadatum.package_channel, metadatum.package_username, and package_type)" do
+ new_package = build('conan_package', project: package.project, name: package.name, version: package.version)
+ expect(new_package).not_to be_valid
+ expect(new_package.errors.to_a).to include("Package recipe already exists")
+ end
+ end
+
+ Packages::Package.package_types.keys.without('conan').each do |pt|
+ context "project id, name, version and package type uniqueness for package type #{pt}" do
+ let(:package) { create("#{pt}_package") }
+
+ it "will not allow a #{pt} package with same project, name, version and package_type" do
+ new_package = build("#{pt}_package", project: package.project, name: package.name, version: package.version)
+ expect(new_package).not_to be_valid
+ expect(new_package.errors.to_a).to include("Name has already been taken")
+ end
+ end
+ end
+ end
+
+ describe '#destroy' do
+ let(:package) { create(:npm_package) }
+ let(:package_file) { package.package_files.first }
+ let(:project_statistics) { ProjectStatistics.for_project_ids(package.project.id).first }
+
+ it 'affects project statistics' do
+ expect { package.destroy! }
+ .to change { project_statistics.reload.packages_size }
+ .from(package_file.size).to(0)
+ end
+ end
+
+ describe '.by_name_and_file_name' do
+ let!(:package) { create(:npm_package) }
+ let!(:package_file) { package.package_files.first }
+
+ subject { described_class }
+
+ it 'finds a package with correct arguiments' do
+ expect(subject.by_name_and_file_name(package.name, package_file.file_name)).to eq(package)
+ end
+
+ it 'will raise error if not found' do
+ expect { subject.by_name_and_file_name('foo', 'foo-5.5.5.tgz') }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context 'version scopes' do
+ let!(:package1) { create(:npm_package, version: '1.0.0') }
+ let!(:package2) { create(:npm_package, version: '1.0.1') }
+ let!(:package3) { create(:npm_package, version: '1.0.1') }
+
+ describe '.last_of_each_version' do
+ subject { described_class.last_of_each_version }
+
+ it 'includes only latest package per version' do
+ is_expected.to include(package1, package3)
+ is_expected.not_to include(package2)
+ end
+ end
+
+ describe '.has_version' do
+ subject { described_class.has_version }
+
+ before do
+ create(:maven_metadatum).package.update!(version: nil)
+ end
+
+ it 'includes only packages with version attribute' do
+ is_expected.to match_array([package1, package2, package3])
+ end
+ end
+
+ describe '.with_version' do
+ subject { described_class.with_version('1.0.1') }
+
+ it 'includes only packages with specified version' do
+ is_expected.to match_array([package2, package3])
+ end
+ end
+
+ describe '.without_version_like' do
+ let(:version_pattern) { '%.0.0%' }
+
+ subject { described_class.without_version_like(version_pattern) }
+
+ it 'includes packages without the version pattern' do
+ is_expected.to match_array([package2, package3])
+ end
+ end
+ end
+
+ context 'conan scopes' do
+ let!(:package) { create(:conan_package) }
+
+ describe '.with_conan_channel' do
+ subject { described_class.with_conan_channel('stable') }
+
+ it 'includes only packages with specified version' do
+ is_expected.to include(package)
+ end
+ end
+
+ describe '.with_conan_username' do
+ subject do
+ described_class.with_conan_username(
+ Packages::Conan::Metadatum.package_username_from(full_path: package.project.full_path)
+ )
+ end
+
+ it 'includes only packages with specified version' do
+ is_expected.to match_array([package])
+ end
+ end
+ end
+
+ describe '.without_nuget_temporary_name' do
+ let!(:package1) { create(:nuget_package) }
+ let!(:package2) { create(:nuget_package, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ subject { described_class.without_nuget_temporary_name }
+
+ it 'does not include nuget temporary packages' do
+ expect(subject).to eq([package1])
+ end
+ end
+
+ describe '.processed' do
+ let!(:package1) { create(:nuget_package) }
+ let!(:package2) { create(:npm_package) }
+ let!(:package3) { create(:nuget_package) }
+
+ subject { described_class.processed }
+
+ it { is_expected.to match_array([package1, package2, package3]) }
+
+ context 'with temporary packages' do
+ let!(:package1) { create(:nuget_package, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
+
+ it { is_expected.to match_array([package2, package3]) }
+ end
+ end
+
+ describe '.limit_recent' do
+ let!(:package1) { create(:nuget_package) }
+ let!(:package2) { create(:nuget_package) }
+ let!(:package3) { create(:nuget_package) }
+
+ subject { described_class.limit_recent(2) }
+
+ it { is_expected.to match_array([package3, package2]) }
+ end
+
+ context 'with several packages' do
+ let_it_be(:package1) { create(:nuget_package, name: 'FooBar') }
+ let_it_be(:package2) { create(:nuget_package, name: 'foobar') }
+ let_it_be(:package3) { create(:npm_package) }
+ let_it_be(:package4) { create(:npm_package) }
+
+ describe '.pluck_names' do
+ subject { described_class.pluck_names }
+
+ it { is_expected.to match_array([package1, package2, package3, package4].map(&:name)) }
+ end
+
+ describe '.pluck_versions' do
+ subject { described_class.pluck_versions }
+
+ it { is_expected.to match_array([package1, package2, package3, package4].map(&:version)) }
+ end
+
+ describe '.with_name_like' do
+ subject { described_class.with_name_like(name_term) }
+
+ context 'with downcase name' do
+ let(:name_term) { 'foobar' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with prefix wildcard' do
+ let(:name_term) { '%ar' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with suffix wildcard' do
+ let(:name_term) { 'foo%' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+
+ context 'with surrounding wildcards' do
+ let(:name_term) { '%ooba%' }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+
+ describe '.search_by_name' do
+ let(:query) { 'oba' }
+
+ subject { described_class.search_by_name(query) }
+
+ it { is_expected.to match_array([package1, package2]) }
+ end
+ end
+
+ describe '.select_distinct_name' do
+ let_it_be(:nuget_package) { create(:nuget_package) }
+ let_it_be(:nuget_packages) { create_list(:nuget_package, 3, name: nuget_package.name, project: nuget_package.project) }
+ let_it_be(:maven_package) { create(:maven_package) }
+ let_it_be(:maven_packages) { create_list(:maven_package, 3, name: maven_package.name, project: maven_package.project) }
+
+ subject { described_class.select_distinct_name }
+
+ it 'returns only distinct names' do
+ packages = subject
+
+ expect(packages.size).to eq(2)
+ expect(packages.pluck(:name)).to match_array([nuget_package.name, maven_package.name])
+ end
+ end
+
+ describe '#versions' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:package) { create(:maven_package, project: project) }
+ let_it_be(:package2) { create(:maven_package, project: project) }
+ let_it_be(:package3) { create(:maven_package, project: project, name: 'foo') }
+
+ it 'returns other package versions of the same package name belonging to the project' do
+ expect(package.versions).to contain_exactly(package2)
+ end
+
+ it 'does not return different packages' do
+ expect(package.versions).not_to include(package3)
+ end
+ end
+
+ describe '#pipeline' do
+ let_it_be(:package) { create(:maven_package) }
+
+ context 'package without pipeline' do
+ it 'returns nil if there is no pipeline' do
+ expect(package.pipeline).to be_nil
+ end
+ end
+
+ context 'package with pipeline' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ before do
+ package.create_build_info!(pipeline: pipeline)
+ end
+
+ it 'returns the pipeline' do
+ expect(package.pipeline).to eq(pipeline)
+ end
+ end
+ end
+
+ describe '#tag_names' do
+ let_it_be(:package) { create(:nuget_package) }
+
+ subject { package.tag_names }
+
+ it { is_expected.to eq([]) }
+
+ context 'with tags' do
+ let(:tags) { %w(tag1 tag2 tag3) }
+
+ before do
+ tags.each { |t| create(:packages_tag, name: t, package: package) }
+ end
+
+ it { is_expected.to contain_exactly(*tags) }
+ end
+ end
+end
diff --git a/spec/models/packages/pypi/metadatum_spec.rb b/spec/models/packages/pypi/metadatum_spec.rb
new file mode 100644
index 00000000000..2c9893ef8f3
--- /dev/null
+++ b/spec/models/packages/pypi/metadatum_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Pypi::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:package) }
+
+ describe '#pypi_package_type' do
+ it 'will not allow a package with a different package_type' do
+ package = build('package')
+ pypi_metadatum = build('pypi_metadatum', package: package)
+
+ expect(pypi_metadatum).not_to be_valid
+ expect(pypi_metadatum.errors.to_a).to include('Package type must be PyPi')
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/sem_ver_spec.rb b/spec/models/packages/sem_ver_spec.rb
new file mode 100644
index 00000000000..419653dca19
--- /dev/null
+++ b/spec/models/packages/sem_ver_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::SemVer, type: :model do
+ shared_examples '#parse with a valid semver' do |str, major, minor, patch, prerelease, build|
+ context "with #{str}" do
+ it "returns #{described_class.new(major, minor, patch, prerelease, build, prefixed: true)} with prefix" do
+ expected = described_class.new(major, minor, patch, prerelease, build, prefixed: true)
+ expect(described_class.parse('v' + str, prefixed: true)).to eq(expected)
+ end
+
+ it "returns #{described_class.new(major, minor, patch, prerelease, build)} without prefix" do
+ expected = described_class.new(major, minor, patch, prerelease, build)
+ expect(described_class.parse(str)).to eq(expected)
+ end
+ end
+ end
+
+ shared_examples '#parse with an invalid semver' do |str|
+ context "with #{str}" do
+ it 'returns nil with prefix' do
+ expect(described_class.parse('v' + str, prefixed: true)).to be_nil
+ end
+
+ it 'returns nil without prefix' do
+ expect(described_class.parse(str)).to be_nil
+ end
+ end
+ end
+
+ describe '#parse' do
+ it_behaves_like '#parse with a valid semver', '1.0.0', 1, 0, 0, nil, nil
+ it_behaves_like '#parse with a valid semver', '1.0.0-pre', 1, 0, 0, 'pre', nil
+ it_behaves_like '#parse with a valid semver', '1.0.0+build', 1, 0, 0, nil, 'build'
+ it_behaves_like '#parse with a valid semver', '1.0.0-pre+build', 1, 0, 0, 'pre', 'build'
+ it_behaves_like '#parse with an invalid semver', '01.0.0'
+ it_behaves_like '#parse with an invalid semver', '0.01.0'
+ it_behaves_like '#parse with an invalid semver', '0.0.01'
+ it_behaves_like '#parse with an invalid semver', '1.0.0asdf'
+ end
+end
diff --git a/spec/models/packages/tag_spec.rb b/spec/models/packages/tag_spec.rb
new file mode 100644
index 00000000000..18ec99c3d51
--- /dev/null
+++ b/spec/models/packages/tag_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Tag, type: :model do
+ let!(:project) { create(:project) }
+ let!(:package) { create(:npm_package, version: '1.0.2', project: project, updated_at: 3.days.ago) }
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:tags) }
+ end
+
+ describe 'validations' do
+ subject { create(:packages_tag) }
+
+ it { is_expected.to validate_presence_of(:package) }
+ it { is_expected.to validate_presence_of(:name) }
+ end
+
+ describe '.for_packages' do
+ let(:package2) { create(:package, project: project, updated_at: 2.days.ago) }
+ let(:package3) { create(:package, project: project, updated_at: 1.day.ago) }
+ let!(:tag1) { create(:packages_tag, package: package) }
+ let!(:tag2) { create(:packages_tag, package: package2) }
+ let!(:tag3) { create(:packages_tag, package: package3) }
+
+ subject { described_class.for_packages(project.packages) }
+
+ it { is_expected.to match_array([tag1, tag2, tag3]) }
+
+ context 'with too many tags' do
+ before do
+ stub_const('Packages::Tag::FOR_PACKAGES_TAGS_LIMIT', 2)
+ end
+
+ it { is_expected.to match_array([tag2, tag3]) }
+ end
+ end
+
+ describe '.with_name' do
+ let_it_be(:package) { create(:package) }
+ let_it_be(:tag1) { create(:packages_tag, package: package, name: 'tag1') }
+ let_it_be(:tag2) { create(:packages_tag, package: package, name: 'tag2') }
+ let_it_be(:tag3) { create(:packages_tag, package: package, name: 'tag3') }
+ let(:name) { 'tag1' }
+
+ subject { described_class.with_name(name) }
+
+ it { is_expected.to contain_exactly(tag1) }
+
+ context 'with nil name' do
+ let(:name) { nil }
+
+ it { is_expected.to eq([]) }
+ end
+
+ context 'with multiple names' do
+ let(:name) { %w(tag1 tag3) }
+
+ it { is_expected.to contain_exactly(tag1, tag3) }
+ end
+ end
+end