Commit 1a0e25f4 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by David Fernandez

Add PyPi to package graphql API

parent 0e0f1aef
......@@ -6,7 +6,7 @@ module Types
graphql_name 'PackageMetadata'
description 'Represents metadata associated with a Package'
possible_types ::Types::Packages::Composer::MetadatumType, ::Types::Packages::Conan::MetadatumType, ::Types::Packages::Maven::MetadatumType, ::Types::Packages::Nuget::MetadatumType
possible_types ::Types::Packages::Composer::MetadatumType, ::Types::Packages::Conan::MetadatumType, ::Types::Packages::Maven::MetadatumType, ::Types::Packages::Nuget::MetadatumType, ::Types::Packages::Pypi::MetadatumType
def self.resolve_type(object, context)
case object
......@@ -18,6 +18,8 @@ module Types
::Types::Packages::Maven::MetadatumType
when ::Packages::Nuget::Metadatum
::Types::Packages::Nuget::MetadatumType
when ::Packages::Pypi::Metadatum
::Types::Packages::Pypi::MetadatumType
else
# NOTE: This method must be kept in sync with `PackageWithoutVersionsType#metadata`,
# which must never produce data that this discriminator cannot handle.
......
......@@ -49,6 +49,8 @@ module Types
object.maven_metadatum
when 'nuget'
object.nuget_metadatum
when 'pypi'
object.pypi_metadatum
else
nil
end
......
# frozen_string_literal: true
module Types
module Packages
module Pypi
class MetadatumType < BaseObject
graphql_name 'PypiMetadata'
description 'Pypi metadata'
authorize :read_package
field :id, ::Types::GlobalIDType[::Packages::Pypi::Metadatum], null: false, description: 'ID of the metadatum.'
field :required_python, GraphQL::STRING_TYPE, null: true, description: 'Required Python version of the Pypi package.'
end
end
end
end
# frozen_string_literal: true
module Packages
module Pypi
class MetadatumPolicy < BasePolicy
delegate { @subject.package }
end
end
end
......@@ -11933,6 +11933,17 @@ Represents rules that commit pushes must follow.
| ---- | ---- | ----------- |
| <a id="pushrulesrejectunsignedcommits"></a>`rejectUnsignedCommits` | [`Boolean!`](#boolean) | Indicates whether commits not signed through GPG will be rejected. |
### `PypiMetadata`
Pypi metadata.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="pypimetadataid"></a>`id` | [`PackagesPypiMetadatumID!`](#packagespypimetadatumid) | ID of the metadatum. |
| <a id="pypimetadatarequiredpython"></a>`requiredPython` | [`String`](#string) | Required Python version of the Pypi package. |
### `RecentFailures`
Recent failure history of a test case.
......@@ -15298,6 +15309,12 @@ A `PackagesPackageID` is a global ID. It is encoded as a string.
An example `PackagesPackageID` is: `"gid://gitlab/Packages::Package/1"`.
### `PackagesPypiMetadatumID`
A `PackagesPypiMetadatumID` is a global ID. It is encoded as a string.
An example `PackagesPypiMetadatumID` is: `"gid://gitlab/Packages::Pypi::Metadatum/1"`.
### `PathLockID`
A `PathLockID` is a global ID. It is encoded as a string.
......@@ -15429,6 +15446,7 @@ One of:
- [`ConanMetadata`](#conanmetadata)
- [`MavenMetadata`](#mavenmetadata)
- [`NugetMetadata`](#nugetmetadata)
- [`PypiMetadata`](#pypimetadata)
#### `VulnerabilityDetail`
......
......@@ -83,6 +83,7 @@
{ "$ref": "./package_conan_metadata.json" },
{ "$ref": "./package_maven_metadata.json" },
{ "$ref": "./package_nuget_metadata.json" },
{ "$ref": "./package_pypi_metadata.json" },
{ "type": "null" }
]
},
......
{
"type": "object",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": {
"type": "string"
},
"requiredPython": {
"type": "string"
}
}
}
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['PypiMetadata'] do
it 'includes pypi metadatum fields' do
expected_fields = %w[
id required_python
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
end
......@@ -3,62 +3,35 @@ require 'spec_helper'
RSpec.describe 'package details' do
include GraphqlHelpers
include_context 'package details setup'
let_it_be(:project) { create(:project) }
let_it_be(:composer_package) { create(:composer_package, project: project) }
let_it_be(:package) { create(:composer_package, project: project) }
let_it_be(:composer_json) { { name: 'name', type: 'type', license: 'license', version: 1 } }
let_it_be(:composer_metadatum) do
# we are forced to manually create the metadatum, without using the factory to force the sha to be a string
# and avoid an error where gitaly can't find the repository
create(:composer_metadatum, package: composer_package, target_sha: 'foo_sha', composer_json: composer_json)
create(:composer_metadatum, package: package, target_sha: 'foo_sha', composer_json: composer_json)
end
let(:depth) { 3 }
let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] }
let(:metadata) { query_graphql_fragment('ComposerMetadata') }
let(:package_files) { all_graphql_fields_for('PackageFile') }
let(:user) { project.owner }
let(:package_global_id) { global_id_of(composer_package) }
let(:package_details) { graphql_data_at(:package) }
let(:metadata_response) { graphql_data_at(:package, :metadata) }
let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) }
let(:query) do
graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
#{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)}
metadata {
#{metadata}
}
packageFiles {
nodes {
#{package_files}
}
}
FIELDS
end
subject { post_graphql(query, current_user: user) }
before do
subject
end
it_behaves_like 'a working graphql query' do
it 'matches the JSON schema' do
expect(package_details).to match_schema('graphql/packages/package_details')
end
end
it_behaves_like 'a package detail'
describe 'Composer' do
it 'has the correct metadata' do
expect(metadata_response).to include(
'targetSha' => 'foo_sha',
'composerJson' => composer_json.transform_keys(&:to_s).transform_values(&:to_s)
)
end
it 'has the correct metadata' do
expect(metadata_response).to include(
'targetSha' => 'foo_sha',
'composerJson' => composer_json.transform_keys(&:to_s).transform_values(&:to_s)
)
end
it 'does not have files' do
expect(package_files_response).to be_empty
end
it 'does not have files' do
expect(package_files_response).to be_empty
end
end
......@@ -3,26 +3,13 @@ require 'spec_helper'
RSpec.describe 'conan package details' do
include GraphqlHelpers
include_context 'package details setup'
let_it_be(:project) { create(:project) }
let_it_be(:conan_package) { create(:conan_package, project: project) }
let_it_be(:package) { create(:conan_package, project: project) }
let(:package_global_id) { global_id_of(conan_package) }
let(:metadata) { query_graphql_fragment('ConanMetadata') }
let(:first_file) { conan_package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } }
let(:depth) { 3 }
let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] }
let(:package_files) { all_graphql_fields_for('PackageFile') }
let(:package_files_metadata) {query_graphql_fragment('ConanFileMetadata')}
let(:user) { project.owner }
let(:package_details) { graphql_data_at(:package) }
let(:metadata_response) { graphql_data_at(:package, :metadata) }
let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) }
let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)}
let(:first_file_response_metadata) { graphql_data_at(:package, :package_files, :nodes, 0, :file_metadata)}
let(:query) do
graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
#{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)}
......@@ -46,35 +33,16 @@ RSpec.describe 'conan package details' do
subject
end
it_behaves_like 'a working graphql query' do
it 'matches the JSON schema' do
expect(package_details).to match_schema('graphql/packages/package_details')
end
end
it_behaves_like 'a package detail'
it_behaves_like 'a package with files'
it 'has the correct metadata' do
expect(metadata_response).to include(
'id' => global_id_of(conan_package.conan_metadatum),
'recipe' => conan_package.conan_metadatum.recipe,
'packageChannel' => conan_package.conan_metadatum.package_channel,
'packageUsername' => conan_package.conan_metadatum.package_username,
'recipePath' => conan_package.conan_metadatum.recipe_path
)
end
it 'has the right amount of files' do
expect(package_files_response.length).to be(conan_package.package_files.length)
end
it 'has the basic package files data' do
expect(first_file_response).to include(
'id' => global_id_of(first_file),
'fileName' => first_file.file_name,
'size' => first_file.size.to_s,
'downloadPath' => first_file.download_path,
'fileSha1' => first_file.file_sha1,
'fileMd5' => first_file.file_md5,
'fileSha256' => first_file.file_sha256
'id' => global_id_of(package.conan_metadatum),
'recipe' => package.conan_metadatum.recipe,
'packageChannel' => package.conan_metadatum.package_channel,
'packageUsername' => package.conan_metadatum.package_username,
'recipePath' => package.conan_metadatum.recipe_path
)
end
......
......@@ -3,89 +3,51 @@ require 'spec_helper'
RSpec.describe 'maven package details' do
include GraphqlHelpers
include_context 'package details setup'
let_it_be(:project) { create(:project) }
let_it_be(:maven_package) { create(:maven_package, project: project) }
let_it_be(:package) { create(:maven_package, project: project) }
let(:package_global_id) { global_id_of(maven_package) }
let(:metadata) { query_graphql_fragment('MavenMetadata') }
let(:first_file) { maven_package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } }
let(:depth) { 3 }
let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] }
let(:package_files) { all_graphql_fields_for('PackageFile') }
let(:user) { project.owner }
let(:package_details) { graphql_data_at(:package) }
let(:metadata_response) { graphql_data_at(:package, :metadata) }
let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) }
let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)}
let(:query) do
graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
#{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)}
metadata {
#{metadata}
}
packageFiles {
nodes {
#{package_files}
}
}
FIELDS
end
subject { post_graphql(query, current_user: user) }
shared_examples 'a working maven package' do
before do
subject
end
it_behaves_like 'a working graphql query' do
it 'matches the JSON schema' do
expect(package_details).to match_schema('graphql/packages/package_details')
end
end
shared_examples 'correct maven metadata' do
it 'has the correct metadata' do
expect(metadata_response).to include(
'id' => global_id_of(maven_package.maven_metadatum),
'path' => maven_package.maven_metadatum.path,
'appGroup' => maven_package.maven_metadatum.app_group,
'appVersion' => maven_package.maven_metadatum.app_version,
'appName' => maven_package.maven_metadatum.app_name
'id' => global_id_of(package.maven_metadatum),
'path' => package.maven_metadatum.path,
'appGroup' => package.maven_metadatum.app_group,
'appVersion' => package.maven_metadatum.app_version,
'appName' => package.maven_metadatum.app_name
)
end
end
it 'has the right amount of files' do
expect(package_files_response.length).to be(maven_package.package_files.length)
end
context 'a maven package with version' do
subject { post_graphql(query, current_user: user) }
it 'has the basic package files data' do
expect(first_file_response).to include(
'id' => global_id_of(first_file),
'fileName' => first_file.file_name,
'size' => first_file.size.to_s,
'downloadPath' => first_file.download_path,
'fileSha1' => first_file.file_sha1,
'fileMd5' => first_file.file_md5,
'fileSha256' => first_file.file_sha256
)
before do
subject
end
end
context 'a maven package with version' do
it_behaves_like "a working maven package"
it_behaves_like 'a package detail'
it_behaves_like 'correct maven metadata'
it_behaves_like 'a package with files'
end
context 'a versionless maven package' do
let_it_be(:maven_metadatum) { create(:maven_metadatum, app_version: nil) }
let_it_be(:maven_package) { create(:maven_package, project: project, version: nil, maven_metadatum: maven_metadatum) }
let_it_be(:package) { create(:maven_package, project: project, version: nil, maven_metadatum: maven_metadatum) }
subject { post_graphql(query, current_user: user) }
before do
subject
end
it_behaves_like "a working maven package"
it_behaves_like 'a package detail'
it_behaves_like 'correct maven metadata'
it_behaves_like 'a package with files'
it "has an empty version" do
it 'has an empty version' do
subject
expect(metadata_response['appVersion']).to eq(nil)
......
......@@ -3,37 +3,11 @@ require 'spec_helper'
RSpec.describe 'nuget package details' do
include GraphqlHelpers
include_context 'package details setup'
let_it_be(:project) { create(:project) }
let_it_be(:nuget_package) { create(:nuget_package, :with_metadatum, project: project) }
let_it_be(:package) { create(:nuget_package, :with_metadatum, project: project) }
let(:package_global_id) { global_id_of(nuget_package) }
let(:metadata) { query_graphql_fragment('NugetMetadata') }
let(:first_file) { nuget_package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } }
let(:depth) { 3 }
let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] }
let(:package_files) { all_graphql_fields_for('PackageFile') }
let(:user) { project.owner }
let(:package_details) { graphql_data_at(:package) }
let(:metadata_response) { graphql_data_at(:package, :metadata) }
let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) }
let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)}
let(:query) do
graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
#{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)}
metadata {
#{metadata}
}
packageFiles {
nodes {
#{package_files}
}
}
FIELDS
end
subject { post_graphql(query, current_user: user) }
......@@ -41,34 +15,15 @@ RSpec.describe 'nuget package details' do
subject
end
it_behaves_like 'a working graphql query' do
it 'matches the JSON schema' do
expect(package_details).to match_schema('graphql/packages/package_details')
end
end
it_behaves_like 'a package detail'
it_behaves_like 'a package with files'
it 'has the correct metadata' do
expect(metadata_response).to include(
'id' => global_id_of(nuget_package.nuget_metadatum),
'licenseUrl' => nuget_package.nuget_metadatum.license_url,
'projectUrl' => nuget_package.nuget_metadatum.project_url,
'iconUrl' => nuget_package.nuget_metadatum.icon_url
)
end
it 'has the right amount of files' do
expect(package_files_response.length).to be(nuget_package.package_files.length)
end
it 'has the basic package files data' do
expect(first_file_response).to include(
'id' => global_id_of(first_file),
'fileName' => first_file.file_name,
'size' => first_file.size.to_s,
'downloadPath' => first_file.download_path,
'fileSha1' => first_file.file_sha1,
'fileMd5' => first_file.file_md5,
'fileSha256' => first_file.file_sha256
'id' => global_id_of(package.nuget_metadatum),
'licenseUrl' => package.nuget_metadatum.license_url,
'projectUrl' => package.nuget_metadatum.project_url,
'iconUrl' => package.nuget_metadatum.icon_url
)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'pypi package details' do
include GraphqlHelpers
include_context 'package details setup'
let_it_be(:package) { create(:pypi_package, project: project) }
let(:metadata) { query_graphql_fragment('PypiMetadata') }
subject { post_graphql(query, current_user: user) }
before do
subject
end
it_behaves_like 'a package detail'
it_behaves_like 'a package with files'
it 'has the correct metadata' do
expect(metadata_response).to include(
'id' => global_id_of(package.pypi_metadatum),
'requiredPython' => package.pypi_metadatum.required_python
)
end
end
# frozen_string_literal: true
RSpec.shared_context 'package details setup' do
let_it_be(:project) { create(:project) }
let_it_be(:package) { create(:package, project: project) }
let(:package_global_id) { global_id_of(package) }
let(:depth) { 3 }
let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline packageFiles] }
let(:package_files) { all_graphql_fields_for('PackageFile') }
let(:user) { project.owner }
let(:package_details) { graphql_data_at(:package) }
let(:metadata_response) { graphql_data_at(:package, :metadata) }
let(:first_file) { package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } }
let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) }
let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)}
let(:first_file_response_metadata) { graphql_data_at(:package, :package_files, :nodes, 0, :file_metadata)}
let(:query) do
graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
#{all_graphql_fields_for('PackageDetailsType', max_depth: depth, excluded: excluded)}
metadata {
#{metadata}
}
packageFiles {
nodes {
#{package_files}
}
}
FIELDS
end
end
# frozen_string_literal: true
RSpec.shared_examples 'a package detail' do
it_behaves_like 'a working graphql query' do
it 'matches the JSON schema' do
expect(package_details).to match_schema('graphql/packages/package_details')
end
end
end
RSpec.shared_examples 'a package with files' do
it 'has the right amount of files' do
expect(package_files_response.length).to be(package.package_files.length)
end
it 'has the basic package files data' do
expect(first_file_response).to include(
'id' => global_id_of(first_file),
'fileName' => first_file.file_name,
'size' => first_file.size.to_s,
'downloadPath' => first_file.download_path,
'fileSha1' => first_file.file_sha1,
'fileMd5' => first_file.file_md5,
'fileSha256' => first_file.file_sha256
)
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