Commit 8ad11b31 authored by David Fernandez's avatar David Fernandez Committed by Mayra Cabrera

Add package tags support to the nuget Repository

Extract them from the nuspec file
Expose them in the nuget API metadata and search endpoints
parent 3b4b79b3
...@@ -137,6 +137,10 @@ class Packages::Package < ApplicationRecord ...@@ -137,6 +137,10 @@ class Packages::Package < ApplicationRecord
build_info&.pipeline build_info&.pipeline
end end
def tag_names
tags.pluck(:name)
end
private private
def valid_conan_package_recipe def valid_conan_package_recipe
......
...@@ -5,8 +5,10 @@ class Packages::Tag < ApplicationRecord ...@@ -5,8 +5,10 @@ class Packages::Tag < ApplicationRecord
validates :package, :name, presence: true validates :package, :name, presence: true
TAGS_LIMIT = 200.freeze TAGS_LIMIT = 200.freeze
NUGET_TAGS_SEPARATOR = ' ' # https://docs.microsoft.com/en-us/nuget/reference/nuspec#tags
scope :preload_package, -> { preload(:package) } scope :preload_package, -> { preload(:package) }
scope :with_name, -> (name) { where(name: name) }
def self.for_packages(packages, max_tags_limit = TAGS_LIMIT) def self.for_packages(packages, max_tags_limit = TAGS_LIMIT)
where(package_id: packages.select(:id)) where(package_id: packages.select(:id))
......
...@@ -16,15 +16,19 @@ module Packages ...@@ -16,15 +16,19 @@ module Packages
def data def data
strong_memoize(:data) do strong_memoize(:data) do
@search.results.group_by(&:name).map do |package_name, packages| @search.results.group_by(&:name).map do |package_name, packages|
latest_version = latest_version(packages)
latest_package = packages.find { |pkg| pkg.version == latest_version }
{ {
type: 'Package', type: 'Package',
authors: '', authors: '',
name: package_name, name: package_name,
version: latest_version(packages), version: latest_version,
versions: build_package_versions(packages), versions: build_package_versions(packages),
summary: '', summary: '',
total_downloads: 0, total_downloads: 0,
verified: true verified: true,
tags: tags_for(latest_package)
} }
end end
end end
......
...@@ -14,6 +14,7 @@ module Packages ...@@ -14,6 +14,7 @@ module Packages
XPATH_DEPENDENCIES = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:dependency' XPATH_DEPENDENCIES = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:dependency'
XPATH_DEPENDENCY_GROUPS = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:group' XPATH_DEPENDENCY_GROUPS = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:group'
XPATH_TAGS = '//xmlns:package/xmlns:metadata/xmlns:tags'
MAX_FILE_SIZE = 4.megabytes.freeze MAX_FILE_SIZE = 4.megabytes.freeze
...@@ -48,6 +49,7 @@ module Packages ...@@ -48,6 +49,7 @@ module Packages
.to_h .to_h
.tap do |metadata| .tap do |metadata|
metadata[:package_dependencies] = extract_dependencies(doc) metadata[:package_dependencies] = extract_dependencies(doc)
metadata[:package_tags] = extract_tags(doc)
end end
end end
...@@ -76,6 +78,14 @@ module Packages ...@@ -76,6 +78,14 @@ module Packages
}.compact }.compact
end end
def extract_tags(doc)
tags = doc.xpath(XPATH_TAGS).text
return [] if tags.blank?
tags.split(::Packages::Tag::NUGET_TAGS_SEPARATOR)
end
def nuspec_file def nuspec_file
package_file.file.use_file do |file_path| package_file.file.use_file do |file_path|
Zip::File.open(file_path) do |zip_file| Zip::File.open(file_path) do |zip_file|
......
...@@ -4,6 +4,10 @@ module Packages ...@@ -4,6 +4,10 @@ module Packages
module Nuget module Nuget
class UpdatePackageFromMetadataService class UpdatePackageFromMetadataService
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include ExclusiveLeaseGuard
# used by ExclusiveLeaseGuard
DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze
InvalidMetadataError = Class.new(StandardError) InvalidMetadataError = Class.new(StandardError)
...@@ -14,24 +18,29 @@ module Packages ...@@ -14,24 +18,29 @@ module Packages
def execute def execute
raise InvalidMetadataError.new('package name and/or package version not found in metadata') unless valid_metadata? raise InvalidMetadataError.new('package name and/or package version not found in metadata') unless valid_metadata?
@package_file.transaction do try_obtain_lease do
if existing_package_id @package_file.transaction do
link_to_existing_package package = existing_package ? link_to_existing_package : update_linked_package
else
update_linked_package update_package(package)
end
# Updating file_name updates the path where the file is stored. # Updating file_name updates the path where the file is stored.
# We must pass the file again so that CarrierWave can handle the update # We must pass the file again so that CarrierWave can handle the update
@package_file.update!( @package_file.update!(
file_name: package_filename, file_name: package_filename,
file: @package_file.file file: @package_file.file
) )
end
end end
end end
private private
def update_package(package)
::Packages::UpdateTagsService.new(package, package_tags)
.execute
end
def valid_metadata? def valid_metadata?
package_name.present? && package_version.present? package_name.present? && package_version.present?
end end
...@@ -41,10 +50,11 @@ module Packages ...@@ -41,10 +50,11 @@ module Packages
# Updating package_id updates the path where the file is stored. # Updating package_id updates the path where the file is stored.
# We must pass the file again so that CarrierWave can handle the update # We must pass the file again so that CarrierWave can handle the update
@package_file.update!( @package_file.update!(
package_id: existing_package_id, package_id: existing_package.id,
file: @package_file.file file: @package_file.file
) )
package_to_destroy.destroy! package_to_destroy.destroy!
existing_package
end end
def update_linked_package def update_linked_package
...@@ -55,15 +65,15 @@ module Packages ...@@ -55,15 +65,15 @@ module Packages
::Packages::Nuget::CreateDependencyService.new(@package_file.package, package_dependencies) ::Packages::Nuget::CreateDependencyService.new(@package_file.package, package_dependencies)
.execute .execute
@package_file.package
end end
def existing_package_id def existing_package
strong_memoize(:existing_package_id) do strong_memoize(:existing_package) do
@package_file.project.packages @package_file.project.packages
.nuget .nuget
.with_name(package_name) .with_name(package_name)
.with_version(package_version) .with_version(package_version)
.pluck_primary_key
.first .first
end end
end end
...@@ -80,6 +90,10 @@ module Packages ...@@ -80,6 +90,10 @@ module Packages
metadata.fetch(:package_dependencies, []) metadata.fetch(:package_dependencies, [])
end end
def package_tags
metadata.fetch(:package_tags, [])
end
def metadata def metadata
strong_memoize(:metadata) do strong_memoize(:metadata) do
::Packages::Nuget::MetadataExtractionService.new(@package_file.id).execute ::Packages::Nuget::MetadataExtractionService.new(@package_file.id).execute
...@@ -89,6 +103,17 @@ module Packages ...@@ -89,6 +103,17 @@ module Packages
def package_filename def package_filename
"#{package_name.downcase}.#{package_version.downcase}.nupkg" "#{package_name.downcase}.#{package_version.downcase}.nupkg"
end end
# used by ExclusiveLeaseGuard
def lease_key
package_id = existing_package ? existing_package.id : @package_file.package.id
"packages:nuget:update_package_from_metadata_service:package:#{package_id}"
end
# used by ExclusiveLeaseGuard
def lease_timeout
DEFAULT_LEASE_TIMEOUT
end
end end
end end
end end
# frozen_string_literal: true
module Packages
class UpdateTagsService
include Gitlab::Utils::StrongMemoize
def initialize(package, tags = [])
@package = package
@tags = tags
end
def execute
return if @tags.empty?
tags_to_destroy = existing_tags - @tags
tags_to_create = @tags - existing_tags
@package.tags.with_name(tags_to_destroy).delete_all if tags_to_destroy.any?
::Gitlab::Database.bulk_insert(Packages::Tag.table_name, rows(tags_to_create)) if tags_to_create.any?
end
private
def existing_tags
strong_memoize(:existing_tags) do
@package.tag_names
end
end
def rows(tags)
now = Time.zone.now
tags.map do |tag|
{
package_id: @package.id,
name: tag,
created_at: now,
updated_at: now
}
end
end
end
end
---
title: Add package tags support to the GitLab NuGet Packages Repository
merge_request: 30726
author:
type: added
...@@ -49,13 +49,18 @@ module API ...@@ -49,13 +49,18 @@ module API
package_name: package.name, package_name: package.name,
package_version: package.version, package_version: package.version,
archive_url: archive_url_for(package), archive_url: archive_url_for(package),
summary: BLANK_STRING summary: BLANK_STRING,
tags: tags_for(package)
} }
end end
def base_path_for(package) def base_path_for(package)
api_v4_projects_packages_nuget_path(id: package.project.id) api_v4_projects_packages_nuget_path(id: package.project.id)
end end
def tags_for(package)
package.tag_names.join(::Packages::Tag::NUGET_TAGS_SEPARATOR)
end
end end
end end
end end
......
...@@ -10,6 +10,7 @@ module EE ...@@ -10,6 +10,7 @@ module EE
expose :dependencies, as: :dependencyGroups expose :dependencies, as: :dependencyGroups
expose :package_name, as: :id expose :package_name, as: :id
expose :package_version, as: :version expose :package_version, as: :version
expose :tags
expose :archive_url, as: :packageContent expose :archive_url, as: :packageContent
expose :summary expose :summary
end end
......
...@@ -14,6 +14,7 @@ module EE ...@@ -14,6 +14,7 @@ module EE
expose :verified expose :verified
expose :version expose :version
expose :versions, using: EE::API::Entities::Nuget::SearchResultVersion expose :versions, using: EE::API::Entities::Nuget::SearchResultVersion
expose :tags
end end
end end
end end
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
"id": { "type": "string" }, "id": { "type": "string" },
"packageContent": { "type": "string" }, "packageContent": { "type": "string" },
"summary": { "const": "" }, "summary": { "const": "" },
"tags": { "type": "string" },
"version": { "type": "string" } "version": { "type": "string" }
} }
} }
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
"id": { "type": "string" }, "id": { "type": "string" },
"packageContent": { "type": "string" }, "packageContent": { "type": "string" },
"summary": { "const": "" }, "summary": { "const": "" },
"tags": { "type": "string" },
"version": { "type": "string" } "version": { "type": "string" }
} }
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
"title": { "type": "string" }, "title": { "type": "string" },
"totalDownloads": { "const": 0 }, "totalDownloads": { "const": 0 },
"verified": { "const": true }, "verified": { "const": true },
"tags": { "type": "string" },
"versions": { "versions": {
"type": "array", "type": "array",
"items": { "items": {
......
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>DummyProject.WithMetadata</id>
<version>1.2.3</version>
<title>nuspec with metadata</title>
<authors>Author Test</authors>
<owners>Author Test</owners>
<developmentDependency>true</developmentDependency>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<licenseUrl>https://opensource.org/licenses/MIT</licenseUrl>
<projectUrl>https://gitlab.com/gitlab-org/gitlab</projectUrl>
<iconUrl>https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png</iconUrl>
<description>Description Test</description>
<releaseNotes>Release Notes Test</releaseNotes>
<copyright>Copyright Test</copyright>
<tags>foo bar test tag1 tag2 tag3 tag4 tag5</tags>
</metadata>
</package>
# frozen_string_literal: true
require 'spec_helper'
describe EE::API::Entities::Nuget::PackageMetadataCatalogEntry do
let(:entry) do
{
json_url: 'http://sandbox.com/json/package',
authors: 'Authors',
dependencies: [],
package_name: 'PackageTest',
package_version: '1.2.3',
tags: 'tag1 tag2 tag3',
archive_url: 'http://sandbox.com/archive/package',
summary: 'Summary'
}
end
let(:expected) do
{
'@id': 'http://sandbox.com/json/package',
'id': 'PackageTest',
'version': '1.2.3',
'authors': 'Authors',
'dependencyGroups': [],
'tags': 'tag1 tag2 tag3',
'packageContent': 'http://sandbox.com/archive/package',
'summary': 'Summary'
}
end
subject { described_class.new(entry).as_json }
it { is_expected.to eq(expected) }
end
# frozen_string_literal: true
require 'spec_helper'
describe EE::API::Entities::Nuget::SearchResult do
let(:search_result) do
{
type: 'Package',
authors: 'Author',
name: 'PackageTest',
version: '1.2.3',
versions: [
{
json_url: 'http://sandbox.com/json/package',
downloads: 100,
version: '1.2.3'
}
],
summary: 'Summary',
total_downloads: 100,
verified: true,
tags: 'tag1 tag2 tag3'
}
end
let(:expected) do
{
'@type': 'Package',
'authors': 'Author',
'id': 'PackageTest',
'title': 'PackageTest',
'summary': 'Summary',
'totalDownloads': 100,
'verified': true,
'version': '1.2.3',
'tags': 'tag1 tag2 tag3',
'versions': [
{
'@id': 'http://sandbox.com/json/package',
'downloads': 100,
'version': '1.2.3'
}
]
}
end
subject { described_class.new(search_result).as_json }
it { is_expected.to eq(expected) }
end
...@@ -421,4 +421,22 @@ RSpec.describe Packages::Package, type: :model do ...@@ -421,4 +421,22 @@ RSpec.describe Packages::Package, type: :model do
end end
end 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 end
...@@ -34,4 +34,28 @@ RSpec.describe Packages::Tag, type: :model do ...@@ -34,4 +34,28 @@ RSpec.describe Packages::Tag, type: :model do
it { is_expected.to match_array([tag2, tag3]) } it { is_expected.to match_array([tag2, tag3]) }
end end
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 end
...@@ -4,6 +4,8 @@ require 'spec_helper' ...@@ -4,6 +4,8 @@ require 'spec_helper'
describe Packages::Nuget::PackageMetadataPresenter do describe Packages::Nuget::PackageMetadataPresenter do
let_it_be(:package) { create(:nuget_package) } let_it_be(:package) { create(:nuget_package) }
let_it_be(:tag1) { create(:packages_tag, name: 'tag1', package: package) }
let_it_be(:tag2) { create(:packages_tag, name: 'tag2', package: package) }
let_it_be(:presenter) { described_class.new(package) } let_it_be(:presenter) { described_class.new(package) }
describe '#json_url' do describe '#json_url' do
...@@ -34,6 +36,7 @@ describe Packages::Nuget::PackageMetadataPresenter do ...@@ -34,6 +36,7 @@ describe Packages::Nuget::PackageMetadataPresenter do
expect(entry[:dependencies]).to eq [] expect(entry[:dependencies]).to eq []
expect(entry[:package_name]).to eq package.name expect(entry[:package_name]).to eq package.name
expect(entry[:package_version]).to eq package.version expect(entry[:package_version]).to eq package.version
expect(entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
end end
end end
end end
...@@ -13,8 +13,16 @@ describe Packages::Nuget::PackagesMetadataPresenter do ...@@ -13,8 +13,16 @@ describe Packages::Nuget::PackagesMetadataPresenter do
end end
describe '#items' do describe '#items' do
let(:tag_names) { %w(tag1 tag2) }
subject { presenter.items } subject { presenter.items }
before do
packages.each do |pkg|
tag_names.each { |tag| create(:packages_tag, package: pkg, name: tag) }
end
end
it 'returns an array' do it 'returns an array' do
items = subject items = subject
...@@ -42,6 +50,7 @@ describe Packages::Nuget::PackagesMetadataPresenter do ...@@ -42,6 +50,7 @@ describe Packages::Nuget::PackagesMetadataPresenter do
%i[json_url archive_url package_name package_version].each { |field| expect(catalog_entry[field]).not_to be_blank } %i[json_url archive_url package_name package_version].each { |field| expect(catalog_entry[field]).not_to be_blank }
%i[authors summary].each { |field| expect(catalog_entry[field]).to be_blank } %i[authors summary].each { |field| expect(catalog_entry[field]).to be_blank }
expect(catalog_entry[:dependencies]).to eq [] expect(catalog_entry[:dependencies]).to eq []
expect(catalog_entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
end end
end end
end end
......
...@@ -5,6 +5,8 @@ require 'spec_helper' ...@@ -5,6 +5,8 @@ require 'spec_helper'
describe Packages::Nuget::SearchResultsPresenter do describe Packages::Nuget::SearchResultsPresenter do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') } let_it_be(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') }
let_it_be(:tag1) { create(:packages_tag, package: package_a, name: 'tag1') }
let_it_be(:tag2) { create(:packages_tag, package: package_a, name: 'tag2') }
let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') } let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') }
let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') } let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
let_it_be(:search_results) { OpenStruct.new(total_count: 3, results: [package_a, packages_b, packages_c].flatten) } let_it_be(:search_results) { OpenStruct.new(total_count: 3, results: [package_a, packages_b, packages_c].flatten) }
...@@ -22,12 +24,13 @@ describe Packages::Nuget::SearchResultsPresenter do ...@@ -22,12 +24,13 @@ describe Packages::Nuget::SearchResultsPresenter do
it 'returns the proper data structure' do it 'returns the proper data structure' do
expect(data.size).to eq 3 expect(data.size).to eq 3
pkg_a, pkg_b, pkg_c = data pkg_a, pkg_b, pkg_c = data
expect_package_result(pkg_a, package_a.name, [package_a.version]) expect_package_result(pkg_a, package_a.name, [package_a.version], %w(tag1 tag2))
expect_package_result(pkg_b, packages_b.first.name, packages_b.map(&:version)) expect_package_result(pkg_b, packages_b.first.name, packages_b.map(&:version))
expect_package_result(pkg_c, packages_c.first.name, packages_c.map(&:version)) expect_package_result(pkg_c, packages_c.first.name, packages_c.map(&:version))
end end
def expect_package_result(package_json, name, versions) # rubocop:disable Metrics/AbcSize
def expect_package_result(package_json, name, versions, tags = [])
expect(package_json[:type]).to eq 'Package' expect(package_json[:type]).to eq 'Package'
expect(package_json[:authors]).to be_blank expect(package_json[:authors]).to be_blank
expect(package_json[:name]).to eq(name) expect(package_json[:name]).to eq(name)
...@@ -40,6 +43,13 @@ describe Packages::Nuget::SearchResultsPresenter do ...@@ -40,6 +43,13 @@ describe Packages::Nuget::SearchResultsPresenter do
expect(version_json[:downloads]).to eq 0 expect(version_json[:downloads]).to eq 0
expect(version_json[:version]).to eq version expect(version_json[:version]).to eq version
end end
if tags.any?
expect(package_json[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly(*tags)
else
expect(package_json[:tags]).to be_blank
end
end end
# rubocop:enable Metrics/AbcSize
end end
end end
...@@ -197,6 +197,7 @@ describe API::NugetPackages do ...@@ -197,6 +197,7 @@ describe API::NugetPackages do
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
let_it_be(:package_name) { 'Dummy.Package' } let_it_be(:package_name) { 'Dummy.Package' }
let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) } let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) }
let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" } let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" }
subject { get api(url) } subject { get api(url) }
...@@ -255,6 +256,7 @@ describe API::NugetPackages do ...@@ -255,6 +256,7 @@ describe API::NugetPackages do
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
let_it_be(:package_name) { 'Dummy.Package' } let_it_be(:package_name) { 'Dummy.Package' }
let_it_be(:package) { create(:nuget_package, name: 'Dummy.Package', project: project) } let_it_be(:package) { create(:nuget_package, name: 'Dummy.Package', project: project) }
let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" } let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" }
subject { get api(url) } subject { get api(url) }
...@@ -431,6 +433,7 @@ describe API::NugetPackages do ...@@ -431,6 +433,7 @@ describe API::NugetPackages do
describe 'GET /api/v4/projects/:id/packages/nuget/query' do describe 'GET /api/v4/projects/:id/packages/nuget/query' do
let_it_be(:package_a) { create(:nuget_package, name: 'Dummy.PackageA', project: project) } let_it_be(:package_a) { create(:nuget_package, name: 'Dummy.PackageA', project: project) }
let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') }
let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) } let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) }
let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) } let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) }
let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) } let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) }
......
...@@ -19,28 +19,37 @@ describe Packages::Nuget::MetadataExtractionService do ...@@ -19,28 +19,37 @@ describe Packages::Nuget::MetadataExtractionService do
target_framework: '.NETCoreApp3.0', target_framework: '.NETCoreApp3.0',
version: '12.0.3' version: '12.0.3'
} }
] ],
package_tags: []
} }
it { is_expected.to eq(expected_metadata) } it { is_expected.to eq(expected_metadata) }
end end
context 'with nuspec file with dependencies' do context 'with nuspec file' do
let(:nuspec_filepath) { 'nuget/with_dependencies.nuspec' }
before do before do
allow(service).to receive(:nuspec_file).and_return(fixture_file(nuspec_filepath, dir: 'ee')) allow(service).to receive(:nuspec_file).and_return(fixture_file(nuspec_filepath, dir: 'ee'))
end end
it { is_expected.to have_key(:package_dependencies) } context 'with dependencies' do
let(:nuspec_filepath) { 'nuget/with_dependencies.nuspec' }
it { is_expected.to have_key(:package_dependencies) }
it 'extracts dependencies' do
dependencies = subject[:package_dependencies]
expect(dependencies).to include(name: 'Moqi', version: '2.5.6')
expect(dependencies).to include(name: 'Castle.Core')
expect(dependencies).to include(name: 'Test.Dependency', version: '2.3.7', target_framework: '.NETStandard2.0')
expect(dependencies).to include(name: 'Newtonsoft.Json', version: '12.0.3', target_framework: '.NETStandard2.0')
end
end
it 'extracts dependencies' do context 'with a nuspec file with metadata' do
dependencies = subject[:package_dependencies] let(:nuspec_filepath) { 'nuget/with_metadata.nuspec' }
expect(dependencies).to include(name: 'Moqi', version: '2.5.6') it { expect(subject[:package_tags].sort).to eq(%w(foo bar test tag1 tag2 tag3 tag4 tag5).sort) }
expect(dependencies).to include(name: 'Castle.Core')
expect(dependencies).to include(name: 'Test.Dependency', version: '2.3.7', target_framework: '.NETStandard2.0')
expect(dependencies).to include(name: 'Newtonsoft.Json', version: '12.0.3', target_framework: '.NETStandard2.0')
end end
end end
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
require 'spec_helper' require 'spec_helper'
describe Packages::Nuget::UpdatePackageFromMetadataService do describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let(:package) { create(:nuget_package) } let(:package) { create(:nuget_package) }
let(:package_file) { package.package_files.first } let(:package_file) { package.package_files.first }
let(:service) { described_class.new(package_file) } let(:service) { described_class.new(package_file) }
...@@ -17,22 +19,74 @@ describe Packages::Nuget::UpdatePackageFromMetadataService do ...@@ -17,22 +19,74 @@ describe Packages::Nuget::UpdatePackageFromMetadataService do
stub_package_file_object_storage(enabled: true, direct_upload: true) stub_package_file_object_storage(enabled: true, direct_upload: true)
end end
it 'updates package and package file' do RSpec.shared_examples 'taking the lease' do
expect { subject } before do
.to change { Packages::Dependency.count }.by(1) allow(service).to receive(:lease_release?).and_return(false)
.and change { Packages::DependencyLink.count }.by(1) end
it 'takes the lease' do
expect(service).to receive(:try_obtain_lease).and_call_original
subject
expect(service.exclusive_lease.exists?).to be_truthy
end
end
RSpec.shared_examples 'not updating the package if the lease is taken' do
context 'without obtaining the exclusive lease' do
let(:lease_key) { "packages:nuget:update_package_from_metadata_service:package:#{package_id}" }
let(:metadata) { { package_name: package_name, package_version: package_version } }
let(:package_from_package_file) { package_file.package }
before do
stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
# to allow the above stub, we need to stub the metadata function as the
# original implementation will try to get an exclusive lease on the
# file in object storage
allow(service).to receive(:metadata).and_return(metadata)
end
it 'does not update the package' do
expect(service).to receive(:try_obtain_lease).and_call_original
expect { subject }
.to change { ::Packages::Package.count }.by(0)
.and change { Packages::DependencyLink.count }.by(0)
expect(package_file.reload.file_name).not_to eq(package_file_name)
expect(package_file.package.reload.name).not_to eq(package_name)
expect(package_file.package.version).not_to eq(package_version)
end
end
end
context 'with no existing package' do
let(:package_id) { package.id }
it 'updates package and package file' do
expect { subject }
.to change { Packages::Dependency.count }.by(1)
.and change { Packages::DependencyLink.count }.by(1)
expect(package.reload.name).to eq(package_name)
expect(package.version).to eq(package_version)
expect(package_file.reload.file_name).to eq(package_file_name)
# hard reset needed to properly reload package_file.file
expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
end
it_behaves_like 'taking the lease'
expect(package.reload.name).to eq(package_name) it_behaves_like 'not updating the package if the lease is taken'
expect(package.version).to eq(package_version)
expect(package_file.reload.file_name).to eq(package_file_name)
# hard reset needed to properly reload package_file.file
expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
end end
context 'with exisiting package' do context 'with existing package' do
let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) } let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) }
let(:package_id) { existing_package.id }
it 'link existing package and updates package file' do it 'link existing package and updates package file' do
expect(service).to receive(:try_obtain_lease).and_call_original
expect { subject } expect { subject }
.to change { ::Packages::Package.count }.by(-1) .to change { ::Packages::Package.count }.by(-1)
.and change { Packages::Dependency.count }.by(0) .and change { Packages::Dependency.count }.by(0)
...@@ -41,6 +95,40 @@ describe Packages::Nuget::UpdatePackageFromMetadataService do ...@@ -41,6 +95,40 @@ describe Packages::Nuget::UpdatePackageFromMetadataService do
expect(package_file.reload.file_name).to eq(package_file_name) expect(package_file.reload.file_name).to eq(package_file_name)
expect(package_file.package).to eq(existing_package) expect(package_file.package).to eq(existing_package)
end end
it_behaves_like 'taking the lease'
it_behaves_like 'not updating the package if the lease is taken'
end
context 'with a nuspec file with metadata' do
let(:nuspec_filepath) { 'nuget/with_metadata.nuspec' }
let(:expected_tags) { %w(foo bar test tag1 tag2 tag3 tag4 tag5) }
before do
allow_any_instance_of(Packages::Nuget::MetadataExtractionService)
.to receive(:nuspec_file)
.and_return(fixture_file(nuspec_filepath, dir: 'ee'))
end
it 'creates tags' do
expect(service).to receive(:try_obtain_lease).and_call_original
expect { subject }.to change { ::Packages::Tag.count }.by(8)
expect(package.reload.tags.map(&:name)).to contain_exactly(*expected_tags)
end
context 'with existing package and tags' do
let!(:existing_package) { create(:nuget_package, project: package.project, name: 'DummyProject.WithMetadata', version: '1.2.3') }
let!(:tag1) { create(:packages_tag, package: existing_package, name: 'tag1') }
let!(:tag2) { create(:packages_tag, package: existing_package, name: 'tag2') }
let!(:tag3) { create(:packages_tag, package: existing_package, name: 'tag_not_in_metadata') }
it 'creates tags and deletes those not in metadata' do
expect(service).to receive(:try_obtain_lease).and_call_original
expect { subject }.to change { ::Packages::Tag.count }.by(5)
expect(existing_package.tags.map(&:name)).to contain_exactly(*expected_tags)
end
end
end end
context 'with nuspec file with dependencies' do context 'with nuspec file with dependencies' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Packages::UpdateTagsService do
let_it_be(:package, reload: true) { create(:nuget_package) }
let(:tags) { %w(test-tag tag1 tag2 tag3) }
let(:service) { described_class.new(package, tags) }
describe '#execute' do
subject { service.execute }
RSpec.shared_examples 'updating tags' do |tags_count|
it 'updates a tag' do
expect { subject }.to change { Packages::Tag.count }.by(tags_count)
expect(package.reload.tags.map(&:name)).to contain_exactly(*tags)
end
end
it_behaves_like 'updating tags', 4
context 'with an existing tag' do
before do
create(:packages_tag, package: package2, name: 'test-tag')
end
context 'on the same package' do
let_it_be(:package2) { package }
it_behaves_like 'updating tags', 3
context 'with different name' do
before do
create(:packages_tag, package: package2, name: 'to_be_destroyed')
end
it_behaves_like 'updating tags', 2
end
end
context 'on a different package' do
let_it_be(:package2) { create(:nuget_package) }
it_behaves_like 'updating tags', 4
end
end
context 'with empty tags' do
let(:tags) { [] }
it 'is a no op' do
expect(package).not_to receive(:tags)
expect(::Gitlab::Database).not_to receive(:bulk_insert)
subject
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment