Commit 65ffed50 authored by David Fernandez's avatar David Fernandez Committed by Mayra Cabrera

Properly expose nuget dependencies in the API

Update nuget API presenters to read package dependencies and expose
them according to
https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#package-dependency-group
parent 78b6c3e7
......@@ -15,4 +15,5 @@ class Packages::DependencyLink < ApplicationRecord
scope :includes_dependency, -> { includes(:dependency) }
scope :for_package, ->(package) { where(package_id: package.id) }
scope :preload_dependency, -> { preload(:dependency) }
scope :preload_nuget_metadatum, -> { preload(:nuget_metadatum) }
end
......@@ -6,7 +6,8 @@ module Packages
include ::API::Helpers::RelatedResourcesHelpers
BLANK_STRING = ''
EMPTY_ARRAY = [].freeze
PACKAGE_DEPENDENCY_GROUP = 'PackageDependencyGroup'
PACKAGE_DEPENDENCY = 'PackageDependency'
private
......@@ -42,7 +43,7 @@ module Packages
{
json_url: json_url_for(package),
authors: BLANK_STRING,
dependencies: EMPTY_ARRAY,
dependency_groups: dependency_groups_for(package),
package_name: package.name,
package_version: package.version,
archive_url: archive_url_for(package),
......@@ -52,6 +53,46 @@ module Packages
}
end
def dependency_groups_for(package)
base_nuget_id = "#{json_url_for(package)}#dependencyGroup"
dependency_links_grouped_by_target_framework(package).map do |target_framework, dependency_links|
nuget_id = target_framework_nuget_id(base_nuget_id, target_framework)
{
id: nuget_id,
type: PACKAGE_DEPENDENCY_GROUP,
target_framework: target_framework,
dependencies: dependencies_for(nuget_id, dependency_links)
}.compact
end
end
def dependency_links_grouped_by_target_framework(package)
package
.dependency_links
.includes_dependency
.preload_nuget_metadatum
.group_by { |dependency_link| dependency_link.nuget_metadatum&.target_framework }
end
def dependencies_for(nuget_id, dependency_links)
return [] if dependency_links.empty?
dependency_links.map do |dependency_link|
dependency = dependency_link.dependency
{
id: "#{nuget_id}/#{dependency.name.downcase}",
type: PACKAGE_DEPENDENCY,
name: dependency.name,
range: dependency.version_pattern
}
end
end
def target_framework_nuget_id(base_nuget_id, target_framework)
target_framework.blank? ? base_nuget_id : "#{base_nuget_id}/#{target_framework.downcase}"
end
def metadatum_for(package)
metadatum = package.nuget_metadatum
return {} unless metadatum
......
---
title: Support nuget dependencies in the API
merge_request: 33389
author:
type: fixed
# frozen_string_literal: true
module EE
module API
module Entities
module Nuget
class Dependency < Grape::Entity
expose :id, as: :@id
expose :type, as: :@type
expose :name, as: :id
expose :range
end
end
end
end
end
# frozen_string_literal: true
module EE
module API
module Entities
module Nuget
class DependencyGroup < Grape::Entity
expose :id, as: :@id
expose :type, as: :@type
expose :target_framework, as: :targetFramework, expose_nil: false
expose :dependencies, using: EE::API::Entities::Nuget::Dependency
end
end
end
end
end
......@@ -7,7 +7,7 @@ module EE
class PackageMetadataCatalogEntry < Grape::Entity
expose :json_url, as: :@id
expose :authors
expose :dependencies, as: :dependencyGroups
expose :dependency_groups, as: :dependencyGroups, using: EE::API::Entities::Nuget::DependencyGroup
expose :package_name, as: :id
expose :package_version, as: :version
expose :tags
......
{
"type": "object",
"required": ["@id", "@type", "dependencies"],
"properties": {
"@id": { "type": "string" },
"@type": { "const": "PackageDependencyGroup" },
"targetFramework": { "type": "string" },
"dependencies": {
"type": "array",
"items": {
"type": "object",
"required": ["@id", "@type", "id", "range"],
"properties": {
"@id": { "type": "string" },
"@type": { "const": "PackageDependency" },
"id": { "type": "string" },
"range": { "type": "string" }
}
}
}
}
}
......@@ -10,7 +10,6 @@
"properties": {
"@id": { "type": "string" },
"authors": { "const": "" },
"dependencyGroups": { "const": [] },
"id": { "type": "string" },
"packageContent": { "type": "string" },
"summary": { "const": "" },
......@@ -18,7 +17,11 @@
"projectUrl": { "type": "string" },
"licenseUrl": { "type": "string" },
"iconUrl": { "type": "string" },
"version": { "type": "string" }
"version": { "type": "string" },
"dependencyGroups": {
"type": "array",
"items": { "$ref": "./dependency_group.json" }
}
}
}
}
......
......@@ -26,7 +26,6 @@
"properties": {
"@id": { "type": "string" },
"authors": { "const": "" },
"dependencyGroups": { "const": [] },
"id": { "type": "string" },
"packageContent": { "type": "string" },
"summary": { "const": "" },
......@@ -34,7 +33,11 @@
"projectUrl": { "type": "string" },
"licenseUrl": { "type": "string" },
"iconUrl": { "type": "string" },
"version": { "type": "string" }
"version": { "type": "string" },
"dependencyGroups": {
"type": "array",
"items": { "$ref": "./dependency_group.json" }
}
}
}
}
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::API::Entities::Nuget::DependencyGroup do
let(:dependency_group) do
{
id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependencygroup',
type: 'PackageDependencyGroup',
target_framework: 'fwk test',
dependencies: [
{
id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
type: 'PackageDependency',
name: 'Dependency',
range: '2.0.0'
}
]
}
end
let(:expected) do
{
'@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependencygroup',
'@type': 'PackageDependencyGroup',
'targetFramework': 'fwk test',
'dependencies': [
{
'@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
'@type': 'PackageDependency',
'id': 'Dependency',
'range': '2.0.0'
}
]
}
end
let(:entity) { described_class.new(dependency_group) }
subject { entity.as_json }
it { is_expected.to eq(expected) }
context 'dependency group without target framework' do
let(:dependency_group_with_no_target_framework) { dependency_group.tap { |dg| dg[:target_framework] = nil } }
let(:expected_no_target_framework) { expected.except(:targetFramework) }
let(:entity) { described_class.new(dependency_group_with_no_target_framework) }
it { is_expected.to eq(expected_no_target_framework) }
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::API::Entities::Nuget::Dependency do
let(:dependency) do
{
id: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
type: 'PackageDependency',
name: 'Dependency',
range: '2.0.0'
}
end
let(:expected) do
{
'@id': 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency',
'@type': 'PackageDependency',
'id': 'Dependency',
'range': '2.0.0'
}
end
let(:entity) { described_class.new(dependency) }
subject { entity.as_json }
it { is_expected.to eq(expected) }
end
......@@ -7,7 +7,7 @@ RSpec.describe EE::API::Entities::Nuget::PackageMetadataCatalogEntry do
{
json_url: 'http://sandbox.com/json/package',
authors: 'Authors',
dependencies: [],
dependency_groups: [],
package_name: 'PackageTest',
package_version: '1.2.3',
tags: 'tag1 tag2 tag3',
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Packages::Nuget::PackageMetadataPresenter do
include_context 'with expected presenters dependency groups'
let_it_be(:package) { create(:nuget_package, :with_metadatum) }
let_it_be(:tag1) { create(:packages_tag, name: 'tag1', package: package) }
let_it_be(:tag2) { create(:packages_tag, name: 'tag2', package: package) }
......@@ -27,13 +29,17 @@ RSpec.describe Packages::Nuget::PackageMetadataPresenter do
describe '#catalog_entry' do
subject { presenter.catalog_entry }
before do
create_dependencies_for(package)
end
it 'returns an entry structure' do
entry = subject
expect(entry).to be_a Hash
%i[json_url archive_url].each { |field| expect(entry[field]).not_to be_blank }
%i[authors summary].each { |field| expect(entry[field]).to be_blank }
expect(entry[:dependencies]).to eq []
expect(entry[:dependency_groups]).to eq expected_dependency_groups(package.project_id, package.name, package.version)
expect(entry[:package_name]).to eq package.name
expect(entry[:package_version]).to eq package.version
expect(entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
......
......@@ -3,7 +3,10 @@
require 'spec_helper'
RSpec.describe Packages::Nuget::PackagesMetadataPresenter do
let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: 'Dummy.Package') }
include_context 'with expected presenters dependency groups'
let_it_be(:project) { create(:project) }
let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: 'Dummy.Package', project: project) }
let_it_be(:presenter) { described_class.new(packages) }
describe '#count' do
......@@ -20,6 +23,8 @@ RSpec.describe Packages::Nuget::PackagesMetadataPresenter do
before do
packages.each do |pkg|
tag_names.each { |tag| create(:packages_tag, package: pkg, name: tag) }
create_dependencies_for(pkg)
end
end
......@@ -49,7 +54,7 @@ RSpec.describe Packages::Nuget::PackagesMetadataPresenter do
catalog_entry = pkg[:catalog_entry]
%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 }
expect(catalog_entry[:dependencies]).to eq []
expect(catalog_entry[:dependency_groups]).to eq(expected_dependency_groups(project.id, catalog_entry[:package_name], catalog_entry[:package_version]))
expect(catalog_entry[:tags].split(::Packages::Tag::NUGET_TAGS_SEPARATOR)).to contain_exactly('tag1', 'tag2')
%i[project_url license_url icon_url].each do |field|
......
......@@ -203,6 +203,8 @@ RSpec.describe API::NugetPackages do
end
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
include_context 'with expected presenters dependency groups'
let_it_be(:package_name) { 'Dummy.Package' }
let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
......@@ -210,6 +212,10 @@ RSpec.describe API::NugetPackages do
subject { get api(url) }
before do
packages.each { |pkg| create_dependencies_for(pkg) }
end
context 'with packages features enabled' do
before do
stub_licensed_features(packages: true)
......@@ -264,6 +270,8 @@ RSpec.describe API::NugetPackages do
end
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
include_context 'with expected presenters dependency groups'
let_it_be(:package_name) { 'Dummy.Package' }
let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
......@@ -271,6 +279,10 @@ RSpec.describe API::NugetPackages do
subject { get api(url) }
before do
create_dependencies_for(package)
end
context 'with packages features enabled' do
before do
stub_licensed_features(packages: true)
......
# frozen_string_literal: true
RSpec.shared_context 'with expected presenters dependency groups' do
def expected_dependency_groups(project_id, package_name, package_version)
[
{
id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0",
target_framework: '.NETStandard2.0',
type: 'PackageDependencyGroup',
dependencies: [
{
id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/.netstandard2.0/newtonsoft.json",
range: '12.0.3',
name: 'Newtonsoft.Json',
type: 'PackageDependency'
}
]
},
{
id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup",
type: 'PackageDependencyGroup',
dependencies: [
{
id: "http://localhost/api/v4/projects/#{project_id}/packages/nuget/metadata/#{package_name}/#{package_version}.json#dependencyGroup/castle.core",
range: '4.4.1',
name: 'Castle.Core',
type: 'PackageDependency'
}
]
}
]
end
def create_dependencies_for(package)
dependency1 = Packages::Dependency.find_by(name: 'Newtonsoft.Json', version_pattern: '12.0.3') || create(:packages_dependency, name: 'Newtonsoft.Json', version_pattern: '12.0.3')
dependency2 = Packages::Dependency.find_by(name: 'Castle.Core', version_pattern: '4.4.1') || create(:packages_dependency, name: 'Castle.Core', version_pattern: '4.4.1')
create(:packages_dependency_link, :with_nuget_metadatum, package: package, dependency: dependency1)
create(:packages_dependency_link, package: package, dependency: dependency2)
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