Improve nuget/rubygems package registries (#34741)

1. Add some missing (optional) fields for nuget v2, and sort the fields
to make it easier to maintain
2. Add missing "platform" for rubygems: `VERSION-PLATFORM` and
`VERSION_PLATFORM`

Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
wxiaoguang 2025-06-18 01:42:00 +08:00 committed by GitHub
parent 224aa64cd9
commit f214bb40a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 335 additions and 136 deletions

View File

@ -44,7 +44,7 @@ func (opts *BlobSearchOptions) toConds() builder.Cond {
cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Tag)}) cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Tag)})
} }
if opts.IsManifest { if opts.IsManifest {
cond = cond.And(builder.Eq{"package_file.lower_name": ManifestFilename}) cond = cond.And(builder.Eq{"package_file.lower_name": container_module.ManifestFilename})
} }
if opts.OnlyLead { if opts.OnlyLead {
cond = cond.And(builder.Eq{"package_file.is_lead": true}) cond = cond.And(builder.Eq{"package_file.is_lead": true})
@ -235,7 +235,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack
func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) { func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) {
var cond builder.Cond = builder.Eq{ var cond builder.Cond = builder.Eq{
"package_version.is_internal": true, "package_version.is_internal": true,
"package_version.lower_version": UploadVersion, "package_version.lower_version": container_module.UploadVersion,
"package.type": packages.TypeContainer, "package.type": packages.TypeContainer,
} }
cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-olderThan).Unix()}) cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-olderThan).Unix()})

View File

@ -103,10 +103,10 @@ func (pd *PackageDescriptor) CalculateBlobSize() int64 {
// GetPackageDescriptor gets the package description for a version // GetPackageDescriptor gets the package description for a version
func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDescriptor, error) { func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDescriptor, error) {
return getPackageDescriptor(ctx, pv, cache.NewEphemeralCache()) return GetPackageDescriptorWithCache(ctx, pv, cache.NewEphemeralCache())
} }
func getPackageDescriptor(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) { func GetPackageDescriptorWithCache(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) {
p, err := cache.GetWithEphemeralCache(ctx, c, "package", pv.PackageID, GetPackageByID) p, err := cache.GetWithEphemeralCache(ctx, c, "package", pv.PackageID, GetPackageByID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -270,7 +270,7 @@ func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*Packa
func getPackageDescriptors(ctx context.Context, pvs []*PackageVersion, c *cache.EphemeralCache) ([]*PackageDescriptor, error) { func getPackageDescriptors(ctx context.Context, pvs []*PackageVersion, c *cache.EphemeralCache) ([]*PackageDescriptor, error) {
pds := make([]*PackageDescriptor, 0, len(pvs)) pds := make([]*PackageDescriptor, 0, len(pvs))
for _, pv := range pvs { for _, pv := range pvs {
pd, err := getPackageDescriptor(ctx, pv, c) pd, err := GetPackageDescriptorWithCache(ctx, pv, c)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -57,14 +57,24 @@ type Package struct {
// Metadata represents the metadata of a Nuget package // Metadata represents the metadata of a Nuget package
type Metadata struct { type Metadata struct {
Description string `json:"description,omitempty"` Authors string `json:"authors,omitempty"`
ReleaseNotes string `json:"release_notes,omitempty"` Copyright string `json:"copyright,omitempty"`
Readme string `json:"readme,omitempty"` Description string `json:"description,omitempty"`
Authors string `json:"authors,omitempty"` DevelopmentDependency bool `json:"development_dependency,omitempty"`
ProjectURL string `json:"project_url,omitempty"` IconURL string `json:"icon_url,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"` Language string `json:"language,omitempty"`
RequireLicenseAcceptance bool `json:"require_license_acceptance"` LicenseURL string `json:"license_url,omitempty"`
Dependencies map[string][]Dependency `json:"dependencies,omitempty"` MinClientVersion string `json:"min_client_version,omitempty"`
Owners string `json:"owners,omitempty"`
ProjectURL string `json:"project_url,omitempty"`
Readme string `json:"readme,omitempty"`
ReleaseNotes string `json:"release_notes,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
RequireLicenseAcceptance bool `json:"require_license_acceptance"`
Tags string `json:"tags,omitempty"`
Title string `json:"title,omitempty"`
Dependencies map[string][]Dependency `json:"dependencies,omitempty"`
} }
// Dependency represents a dependency of a Nuget package // Dependency represents a dependency of a Nuget package
@ -74,24 +84,30 @@ type Dependency struct {
} }
// https://learn.microsoft.com/en-us/nuget/reference/nuspec // https://learn.microsoft.com/en-us/nuget/reference/nuspec
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Packaging/compiler/resources/nuspec.xsd
type nuspecPackage struct { type nuspecPackage struct {
Metadata struct { Metadata struct {
ID string `xml:"id"` // required fields
Version string `xml:"version"` Authors string `xml:"authors"`
Authors string `xml:"authors"` Description string `xml:"description"`
RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"` ID string `xml:"id"`
Version string `xml:"version"`
// optional fields
Copyright string `xml:"copyright"`
DevelopmentDependency bool `xml:"developmentDependency"`
IconURL string `xml:"iconUrl"`
Language string `xml:"language"`
LicenseURL string `xml:"licenseUrl"`
MinClientVersion string `xml:"minClientVersion,attr"`
Owners string `xml:"owners"`
ProjectURL string `xml:"projectUrl"` ProjectURL string `xml:"projectUrl"`
Description string `xml:"description"`
ReleaseNotes string `xml:"releaseNotes"`
Readme string `xml:"readme"` Readme string `xml:"readme"`
PackageTypes struct { ReleaseNotes string `xml:"releaseNotes"`
PackageType []struct { RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"`
Name string `xml:"name,attr"` Tags string `xml:"tags"`
} `xml:"packageType"` Title string `xml:"title"`
} `xml:"packageTypes"`
Repository struct {
URL string `xml:"url,attr"`
} `xml:"repository"`
Dependencies struct { Dependencies struct {
Dependency []struct { Dependency []struct {
ID string `xml:"id,attr"` ID string `xml:"id,attr"`
@ -107,6 +123,14 @@ type nuspecPackage struct {
} `xml:"dependency"` } `xml:"dependency"`
} `xml:"group"` } `xml:"group"`
} `xml:"dependencies"` } `xml:"dependencies"`
PackageTypes struct {
PackageType []struct {
Name string `xml:"name,attr"`
} `xml:"packageType"`
} `xml:"packageTypes"`
Repository struct {
URL string `xml:"url,attr"`
} `xml:"repository"`
} `xml:"metadata"` } `xml:"metadata"`
} }
@ -167,13 +191,23 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
} }
m := &Metadata{ m := &Metadata{
Description: p.Metadata.Description,
ReleaseNotes: p.Metadata.ReleaseNotes,
Authors: p.Metadata.Authors, Authors: p.Metadata.Authors,
Copyright: p.Metadata.Copyright,
Description: p.Metadata.Description,
DevelopmentDependency: p.Metadata.DevelopmentDependency,
IconURL: p.Metadata.IconURL,
Language: p.Metadata.Language,
LicenseURL: p.Metadata.LicenseURL,
MinClientVersion: p.Metadata.MinClientVersion,
Owners: p.Metadata.Owners,
ProjectURL: p.Metadata.ProjectURL, ProjectURL: p.Metadata.ProjectURL,
ReleaseNotes: p.Metadata.ReleaseNotes,
RepositoryURL: p.Metadata.Repository.URL, RepositoryURL: p.Metadata.Repository.URL,
RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance, RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
Dependencies: make(map[string][]Dependency), Tags: p.Metadata.Tags,
Title: p.Metadata.Title,
Dependencies: make(map[string][]Dependency),
} }
if p.Metadata.Readme != "" { if p.Metadata.Readme != "" {
@ -227,13 +261,13 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
func toNormalizedVersion(v *version.Version) string { func toNormalizedVersion(v *version.Version) string {
var buf bytes.Buffer var buf bytes.Buffer
segments := v.Segments64() segments := v.Segments64()
fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2]) _, _ = fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
if len(segments) > 3 && segments[3] > 0 { if len(segments) > 3 && segments[3] > 0 {
fmt.Fprintf(&buf, ".%d", segments[3]) _, _ = fmt.Fprintf(&buf, ".%d", segments[3])
} }
pre := v.Prerelease() pre := v.Prerelease()
if pre != "" { if pre != "" {
fmt.Fprint(&buf, "-", pre) _, _ = fmt.Fprint(&buf, "-", pre)
} }
return buf.String() return buf.String()
} }

View File

@ -12,44 +12,62 @@ import (
) )
const ( const (
id = "System.Gitea" authors = "Gitea Authors"
semver = "1.0.1" copyright = "Package Copyright"
authors = "Gitea Authors" dependencyID = "System.Text.Json"
projectURL = "https://gitea.io" dependencyVersion = "5.0.0"
description = "Package Description" developmentDependency = true
releaseNotes = "Package Release Notes" description = "Package Description"
readme = "Readme" iconURL = "https://gitea.io/favicon.png"
repositoryURL = "https://gitea.io/gitea/gitea" id = "System.Gitea"
targetFramework = ".NETStandard2.1" language = "Package Language"
dependencyID = "System.Text.Json" licenseURL = "https://gitea.io/license"
dependencyVersion = "5.0.0" minClientVersion = "1.0.0.0"
owners = "Package Owners"
projectURL = "https://gitea.io"
readme = "Readme"
releaseNotes = "Package Release Notes"
repositoryURL = "https://gitea.io/gitea/gitea"
requireLicenseAcceptance = true
tags = "tag_1 tag_2 tag_3"
targetFramework = ".NETStandard2.1"
title = "Package Title"
versionStr = "1.0.1"
) )
const nuspecContent = `<?xml version="1.0" encoding="utf-8"?> const nuspecContent = `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata> <metadata minClientVersion="` + minClientVersion + `">
<id>` + id + `</id> <authors>` + authors + `</authors>
<version>` + semver + `</version> <copyright>` + copyright + `</copyright>
<authors>` + authors + `</authors> <description>` + description + `</description>
<requireLicenseAcceptance>true</requireLicenseAcceptance> <developmentDependency>true</developmentDependency>
<projectUrl>` + projectURL + `</projectUrl> <iconUrl>` + iconURL + `</iconUrl>
<description>` + description + `</description> <id>` + id + `</id>
<releaseNotes>` + releaseNotes + `</releaseNotes> <language>` + language + `</language>
<repository url="` + repositoryURL + `" /> <licenseUrl>` + licenseURL + `</licenseUrl>
<readme>README.md</readme> <owners>` + owners + `</owners>
<dependencies> <projectUrl>` + projectURL + `</projectUrl>
<group targetFramework="` + targetFramework + `"> <readme>README.md</readme>
<dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" /> <releaseNotes>` + releaseNotes + `</releaseNotes>
</group> <repository url="` + repositoryURL + `" />
</dependencies> <requireLicenseAcceptance>true</requireLicenseAcceptance>
</metadata> <tags>` + tags + `</tags>
<title>` + title + `</title>
<version>` + versionStr + `</version>
<dependencies>
<group targetFramework="` + targetFramework + `">
<dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
</package>` </package>`
const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?> const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata> <metadata>
<id>` + id + `</id> <id>` + id + `</id>
<version>` + semver + `</version> <version>` + versionStr + `</version>
<description>` + description + `</description> <description>` + description + `</description>
<packageTypes> <packageTypes>
<packageType name="SymbolsPackage" /> <packageType name="SymbolsPackage" />
@ -140,14 +158,26 @@ func TestParsePackageMetaData(t *testing.T) {
assert.NotNil(t, np) assert.NotNil(t, np)
assert.Equal(t, DependencyPackage, np.PackageType) assert.Equal(t, DependencyPackage, np.PackageType)
assert.Equal(t, id, np.ID)
assert.Equal(t, semver, np.Version)
assert.Equal(t, authors, np.Metadata.Authors) assert.Equal(t, authors, np.Metadata.Authors)
assert.Equal(t, projectURL, np.Metadata.ProjectURL)
assert.Equal(t, description, np.Metadata.Description) assert.Equal(t, description, np.Metadata.Description)
assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) assert.Equal(t, id, np.ID)
assert.Equal(t, versionStr, np.Version)
assert.Equal(t, copyright, np.Metadata.Copyright)
assert.Equal(t, developmentDependency, np.Metadata.DevelopmentDependency)
assert.Equal(t, iconURL, np.Metadata.IconURL)
assert.Equal(t, language, np.Metadata.Language)
assert.Equal(t, licenseURL, np.Metadata.LicenseURL)
assert.Equal(t, minClientVersion, np.Metadata.MinClientVersion)
assert.Equal(t, owners, np.Metadata.Owners)
assert.Equal(t, projectURL, np.Metadata.ProjectURL)
assert.Equal(t, readme, np.Metadata.Readme) assert.Equal(t, readme, np.Metadata.Readme)
assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes)
assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL) assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL)
assert.Equal(t, requireLicenseAcceptance, np.Metadata.RequireLicenseAcceptance)
assert.Equal(t, tags, np.Metadata.Tags)
assert.Equal(t, title, np.Metadata.Title)
assert.Len(t, np.Metadata.Dependencies, 1) assert.Len(t, np.Metadata.Dependencies, 1)
assert.Contains(t, np.Metadata.Dependencies, targetFramework) assert.Contains(t, np.Metadata.Dependencies, targetFramework)
deps := np.Metadata.Dependencies[targetFramework] deps := np.Metadata.Dependencies[targetFramework]
@ -180,7 +210,7 @@ func TestParsePackageMetaData(t *testing.T) {
assert.Equal(t, SymbolsPackage, np.PackageType) assert.Equal(t, SymbolsPackage, np.PackageType)
assert.Equal(t, id, np.ID) assert.Equal(t, id, np.ID)
assert.Equal(t, semver, np.Version) assert.Equal(t, versionStr, np.Version)
assert.Equal(t, description, np.Metadata.Description) assert.Equal(t, description, np.Metadata.Description)
assert.Empty(t, np.Metadata.Dependencies) assert.Empty(t, np.Metadata.Dependencies)
}) })

View File

@ -34,7 +34,7 @@ type PortablePdbList []*PortablePdb
func (l PortablePdbList) Close() { func (l PortablePdbList) Close() {
for _, pdb := range l { for _, pdb := range l {
pdb.Content.Close() _ = pdb.Content.Close()
} }
} }
@ -65,7 +65,7 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) {
buf, err := packages.CreateHashedBufferFromReader(f) buf, err := packages.CreateHashedBufferFromReader(f)
f.Close() _ = f.Close()
if err != nil { if err != nil {
return err return err
@ -73,12 +73,12 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) {
id, err := ParseDebugHeaderID(buf) id, err := ParseDebugHeaderID(buf)
if err != nil { if err != nil {
buf.Close() _ = buf.Close()
return fmt.Errorf("Invalid PDB file: %w", err) return fmt.Errorf("Invalid PDB file: %w", err)
} }
if _, err := buf.Seek(0, io.SeekStart); err != nil { if _, err := buf.Seek(0, io.SeekStart); err != nil {
buf.Close() _ = buf.Close()
return err return err
} }

View File

@ -24,14 +24,14 @@ func TestExtractPortablePdb(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
archive := zip.NewWriter(&buf) archive := zip.NewWriter(&buf)
w, _ := archive.Create(name) w, _ := archive.Create(name)
w.Write(content) _, _ = w.Write(content)
archive.Close() _ = archive.Close()
return buf.Bytes() return buf.Bytes()
} }
t.Run("MissingPdbFiles", func(t *testing.T) { t.Run("MissingPdbFiles", func(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
zip.NewWriter(&buf).Close() _ = zip.NewWriter(&buf).Close()
pdbs, err := ExtractPortablePdb(bytes.NewReader(buf.Bytes()), int64(buf.Len())) pdbs, err := ExtractPortablePdb(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
assert.ErrorIs(t, err, ErrMissingPdbFiles) assert.ErrorIs(t, err, ErrMissingPdbFiles)

View File

@ -130,8 +130,8 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI
pv := &packages_model.PackageVersion{ pv := &packages_model.PackageVersion{
PackageID: p.ID, PackageID: p.ID,
CreatorID: pi.Owner.ID, CreatorID: pi.Owner.ID,
Version: container_model.UploadVersion, Version: container_module.UploadVersion,
LowerVersion: container_model.UploadVersion, LowerVersion: container_module.UploadVersion,
IsInternal: true, IsInternal: true,
MetadataJSON: "null", MetadataJSON: "null",
} }

View File

@ -151,7 +151,7 @@ func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf
return err return err
} }
uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_model.UploadVersion) uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_module.UploadVersion)
if err != nil && err != packages_model.ErrPackageNotExist { if err != nil && err != packages_model.ErrPackageNotExist {
return err return err
} }
@ -492,7 +492,7 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack
pf, err := createFileFromBlobReference(ctx, pv, nil, &blobReference{ pf, err := createFileFromBlobReference(ctx, pv, nil, &blobReference{
Digest: digest.Digest(manifestDigest), Digest: digest.Digest(manifestDigest),
MediaType: mci.MediaType, MediaType: mci.MediaType,
Name: container_model.ManifestFilename, Name: container_module.ManifestFilename,
File: &packages_model.PackageFileDescriptor{Blob: pb}, File: &packages_model.PackageFileDescriptor{Blob: pb},
ExpectedSize: pb.Size, ExpectedSize: pb.Size,
IsLead: true, IsLead: true,
@ -505,7 +505,7 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack
OwnerID: mci.Owner.ID, OwnerID: mci.Owner.ID,
PackageType: packages_model.TypeContainer, PackageType: packages_model.TypeContainer,
VersionID: pv.ID, VersionID: pv.ID,
Query: container_model.ManifestFilename, Query: container_module.ManifestFilename,
}) })
if err != nil { if err != nil {
return nil, false, "", err return nil, false, "", err

View File

@ -246,21 +246,30 @@ type TypedValue[T any] struct {
} }
type FeedEntryProperties struct { type FeedEntryProperties struct {
Version string `xml:"d:Version"`
NormalizedVersion string `xml:"d:NormalizedVersion"`
Authors string `xml:"d:Authors"` Authors string `xml:"d:Authors"`
Copyright string `xml:"d:Copyright,omitempty"`
Created TypedValue[time.Time] `xml:"d:Created"`
Dependencies string `xml:"d:Dependencies"` Dependencies string `xml:"d:Dependencies"`
Description string `xml:"d:Description"` Description string `xml:"d:Description"`
VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"` DevelopmentDependency TypedValue[bool] `xml:"d:DevelopmentDependency"`
DownloadCount TypedValue[int64] `xml:"d:DownloadCount"` DownloadCount TypedValue[int64] `xml:"d:DownloadCount"`
PackageSize TypedValue[int64] `xml:"d:PackageSize"` ID string `xml:"d:Id"`
Created TypedValue[time.Time] `xml:"d:Created"` IconURL string `xml:"d:IconUrl,omitempty"`
Language string `xml:"d:Language,omitempty"`
LastUpdated TypedValue[time.Time] `xml:"d:LastUpdated"` LastUpdated TypedValue[time.Time] `xml:"d:LastUpdated"`
Published TypedValue[time.Time] `xml:"d:Published"` LicenseURL string `xml:"d:LicenseUrl,omitempty"`
MinClientVersion string `xml:"d:MinClientVersion,omitempty"`
NormalizedVersion string `xml:"d:NormalizedVersion"`
Owners string `xml:"d:Owners,omitempty"`
PackageSize TypedValue[int64] `xml:"d:PackageSize"`
ProjectURL string `xml:"d:ProjectUrl,omitempty"` ProjectURL string `xml:"d:ProjectUrl,omitempty"`
Published TypedValue[time.Time] `xml:"d:Published"`
ReleaseNotes string `xml:"d:ReleaseNotes,omitempty"` ReleaseNotes string `xml:"d:ReleaseNotes,omitempty"`
RequireLicenseAcceptance TypedValue[bool] `xml:"d:RequireLicenseAcceptance"` RequireLicenseAcceptance TypedValue[bool] `xml:"d:RequireLicenseAcceptance"`
Title string `xml:"d:Title"` Tags string `xml:"d:Tags,omitempty"`
Title string `xml:"d:Title,omitempty"`
Version string `xml:"d:Version"`
VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"`
} }
type FeedEntry struct { type FeedEntry struct {
@ -353,21 +362,30 @@ func createEntry(l *linkBuilder, pd *packages_model.PackageDescriptor, withNames
Author: metadata.Authors, Author: metadata.Authors,
Content: content, Content: content,
Properties: &FeedEntryProperties{ Properties: &FeedEntryProperties{
Version: pd.Version.Version,
NormalizedVersion: pd.Version.Version,
Authors: metadata.Authors, Authors: metadata.Authors,
Copyright: metadata.Copyright,
Created: createdValue,
Dependencies: buildDependencyString(metadata), Dependencies: buildDependencyString(metadata),
Description: metadata.Description, Description: metadata.Description,
VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount}, DevelopmentDependency: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.DevelopmentDependency},
DownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount}, DownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()}, ID: pd.Package.Name,
Created: createdValue, IconURL: metadata.IconURL,
Language: metadata.Language,
LastUpdated: createdValue, LastUpdated: createdValue,
Published: createdValue, LicenseURL: metadata.LicenseURL,
MinClientVersion: metadata.MinClientVersion,
NormalizedVersion: pd.Version.Version,
Owners: metadata.Owners,
PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()},
ProjectURL: metadata.ProjectURL, ProjectURL: metadata.ProjectURL,
Published: createdValue,
ReleaseNotes: metadata.ReleaseNotes, ReleaseNotes: metadata.ReleaseNotes,
RequireLicenseAcceptance: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.RequireLicenseAcceptance}, RequireLicenseAcceptance: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.RequireLicenseAcceptance},
Title: pd.Package.Name, Tags: metadata.Tags,
Title: metadata.Title,
Version: pd.Version.Version,
VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
}, },
} }

View File

@ -14,6 +14,7 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
rubygems_module "code.gitea.io/gitea/modules/packages/rubygems" rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
@ -309,7 +310,7 @@ func GetPackageInfo(ctx *context.Context) {
apiError(ctx, http.StatusNotFound, nil) apiError(ctx, http.StatusNotFound, nil)
return return
} }
infoContent, err := makePackageInfo(ctx, versions) infoContent, err := makePackageInfo(ctx, versions, cache.NewEphemeralCache())
if err != nil { if err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
return return
@ -317,7 +318,7 @@ func GetPackageInfo(ctx *context.Context) {
ctx.PlainText(http.StatusOK, infoContent) ctx.PlainText(http.StatusOK, infoContent)
} }
// GetAllPackagesVersions returns a custom text based format containing information about all versions of all rubygems. // GetAllPackagesVersions returns a custom text-based format containing information about all versions of all rubygems.
// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/ // ref: https://guides.rubygems.org/rubygems-org-compact-index-api/
func GetAllPackagesVersions(ctx *context.Context) { func GetAllPackagesVersions(ctx *context.Context) {
packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems) packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems)
@ -326,6 +327,7 @@ func GetAllPackagesVersions(ctx *context.Context) {
return return
} }
ephemeralCache := cache.NewEphemeralCache()
out := &strings.Builder{} out := &strings.Builder{}
out.WriteString("---\n") out.WriteString("---\n")
for _, pkg := range packages { for _, pkg := range packages {
@ -338,7 +340,7 @@ func GetAllPackagesVersions(ctx *context.Context) {
continue continue
} }
info, err := makePackageInfo(ctx, versions) info, err := makePackageInfo(ctx, versions, ephemeralCache)
if err != nil { if err != nil {
apiError(ctx, http.StatusInternalServerError, err) apiError(ctx, http.StatusInternalServerError, err)
return return
@ -348,7 +350,14 @@ func GetAllPackagesVersions(ctx *context.Context) {
_, _ = fmt.Fprintf(out, "%s ", pkg.Name) _, _ = fmt.Fprintf(out, "%s ", pkg.Name)
for i, v := range versions { for i, v := range versions {
sep := util.Iif(i == len(versions)-1, "", ",") sep := util.Iif(i == len(versions)-1, "", ",")
_, _ = fmt.Fprintf(out, "%s%s", v.Version, sep) pd, err := packages_model.GetPackageDescriptorWithCache(ctx, v, ephemeralCache)
if errors.Is(err, util.ErrNotExist) {
continue
} else if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
writePackageVersionForList(pd.Metadata, v.Version, sep, out)
} }
_, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info))) _, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info)))
} }
@ -356,6 +365,16 @@ func GetAllPackagesVersions(ctx *context.Context) {
ctx.PlainText(http.StatusOK, out.String()) ctx.PlainText(http.StatusOK, out.String())
} }
func writePackageVersionForList(metadata any, version, sep string, out *strings.Builder) {
if metadata, _ := metadata.(*rubygems_module.Metadata); metadata != nil && metadata.Platform != "" && metadata.Platform != "ruby" {
// VERSION_PLATFORM (see comment above in GetAllPackagesVersions)
_, _ = fmt.Fprintf(out, "%s_%s%s", version, metadata.Platform, sep)
} else {
// VERSION only
_, _ = fmt.Fprintf(out, "%s%s", version, sep)
}
}
func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) { func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) {
out.WriteString(prefix) out.WriteString(prefix)
if len(reqs) == 0 { if len(reqs) == 0 {
@ -367,11 +386,21 @@ func writePackageVersionRequirements(prefix string, reqs []rubygems_module.Versi
} }
} }
func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion) (string, error) { func writePackageVersionForDependency(version, platform string, out *strings.Builder) {
if platform != "" && platform != "ruby" {
// VERSION-PLATFORM (see comment below in makePackageVersionDependency)
_, _ = fmt.Fprintf(out, "%s-%s ", version, platform)
} else {
// VERSION only
_, _ = fmt.Fprintf(out, "%s ", version)
}
}
func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) {
// format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...] // format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...]
// DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT] // DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT]
// REQUIREMENT: KEY:VALUE (always contains "checksum") // REQUIREMENT: KEY:VALUE (always contains "checksum")
pd, err := packages_model.GetPackageDescriptor(ctx, version) pd, err := packages_model.GetPackageDescriptorWithCache(ctx, version, c)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -388,8 +417,7 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model.
} }
buf := &strings.Builder{} buf := &strings.Builder{}
buf.WriteString(version.Version) writePackageVersionForDependency(version.Version, metadata.Platform, buf)
buf.WriteByte(' ')
for i, dep := range metadata.RuntimeDependencies { for i, dep := range metadata.RuntimeDependencies {
sep := util.Iif(i == 0, "", ",") sep := util.Iif(i == 0, "", ",")
writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf) writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf)
@ -404,10 +432,10 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model.
return buf.String(), nil return buf.String(), nil
} }
func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion) (string, error) { func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) {
ret := "---\n" ret := "---\n"
for _, v := range versions { for _, v := range versions {
dep, err := makePackageVersionDependency(ctx, v) dep, err := makePackageVersionDependency(ctx, v, c)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -0,0 +1,41 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package rubygems
import (
"strings"
"testing"
rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
"github.com/stretchr/testify/assert"
)
func TestWritePackageVersion(t *testing.T) {
buf := &strings.Builder{}
writePackageVersionForList(nil, "1.0", " ", buf)
assert.Equal(t, "1.0 ", buf.String())
buf.Reset()
writePackageVersionForList(&rubygems_module.Metadata{Platform: "ruby"}, "1.0", " ", buf)
assert.Equal(t, "1.0 ", buf.String())
buf.Reset()
writePackageVersionForList(&rubygems_module.Metadata{Platform: "linux"}, "1.0", " ", buf)
assert.Equal(t, "1.0_linux ", buf.String())
buf.Reset()
writePackageVersionForDependency("1.0", "", buf)
assert.Equal(t, "1.0 ", buf.String())
buf.Reset()
writePackageVersionForDependency("1.0", "ruby", buf)
assert.Equal(t, "1.0 ", buf.String())
buf.Reset()
writePackageVersionForDependency("1.0", "os", buf)
assert.Equal(t, "1.0-os ", buf.String())
buf.Reset()
}

View File

@ -57,7 +57,7 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
Type: packages_model.TypeContainer, Type: packages_model.TypeContainer,
Version: packages_model.SearchValue{ Version: packages_model.SearchValue{
ExactMatch: true, ExactMatch: true,
Value: container_model.UploadVersion, Value: container_module.UploadVersion,
}, },
IsInternal: optional.Some(true), IsInternal: optional.Some(true),
HasFiles: optional.Some(false), HasFiles: optional.Some(false),

View File

@ -18,7 +18,6 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
container_module "code.gitea.io/gitea/modules/packages/container" container_module "code.gitea.io/gitea/modules/packages/container"
@ -71,8 +70,8 @@ func TestPackageContainer(t *testing.T) {
configContent := `{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/true"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"container":"b89fe92a887d55c0961f02bdfbfd8ac3ddf66167db374770d2d9e9fab3311510","container_config":{"Hostname":"b89fe92a887d","Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/true\"]"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"created":"2022-01-01T00:00:00.000000000Z","docker_version":"20.10.12","history":[{"created":"2022-01-01T00:00:00.000000000Z","created_by":"/bin/sh -c #(nop) COPY file:0e7589b0c800daaf6fa460d2677101e4676dd9491980210cb345480e513f3602 in /true "},{"created":"2022-01-01T00:00:00.000000001Z","created_by":"/bin/sh -c #(nop) CMD [\"/true\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:0ff3b91bdf21ecdf2f2f3d4372c2098a14dbe06cd678e8f0a85fd4902d00e2e2"]}}` configContent := `{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/true"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"container":"b89fe92a887d55c0961f02bdfbfd8ac3ddf66167db374770d2d9e9fab3311510","container_config":{"Hostname":"b89fe92a887d","Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/true\"]"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"created":"2022-01-01T00:00:00.000000000Z","docker_version":"20.10.12","history":[{"created":"2022-01-01T00:00:00.000000000Z","created_by":"/bin/sh -c #(nop) COPY file:0e7589b0c800daaf6fa460d2677101e4676dd9491980210cb345480e513f3602 in /true "},{"created":"2022-01-01T00:00:00.000000001Z","created_by":"/bin/sh -c #(nop) CMD [\"/true\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:0ff3b91bdf21ecdf2f2f3d4372c2098a14dbe06cd678e8f0a85fd4902d00e2e2"]}}`
manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6" manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6"
manifestContent := `{"schemaVersion":2,"mediaType":"` + container_model.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` manifestContent := `{"schemaVersion":2,"mediaType":"` + container_module.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}`
manifestContentType := container_model.ContentTypeDockerDistributionManifestV2 manifestContentType := container_module.ContentTypeDockerDistributionManifestV2
untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d" untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d"
untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}`
@ -252,7 +251,7 @@ func TestPackageContainer(t *testing.T) {
assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location")) assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location"))
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_model.UploadVersion) pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_module.UploadVersion)
assert.NoError(t, err) assert.NoError(t, err)
pfs, err := packages_model.GetFilesByVersionID(db.DefaultContext, pv.ID) pfs, err := packages_model.GetFilesByVersionID(db.DefaultContext, pv.ID)
@ -432,7 +431,7 @@ func TestPackageContainer(t *testing.T) {
assert.Len(t, pd.Files, 3) assert.Len(t, pd.Files, 3)
for _, pfd := range pd.Files { for _, pfd := range pd.Files {
switch pfd.File.Name { switch pfd.File.Name {
case container_model.ManifestFilename: case container_module.ManifestFilename:
assert.True(t, pfd.File.IsLead) assert.True(t, pfd.File.IsLead)
assert.Equal(t, "application/vnd.docker.distribution.manifest.v2+json", pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, "application/vnd.docker.distribution.manifest.v2+json", pfd.Properties.GetByName(container_module.PropertyMediaType))
assert.Equal(t, manifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) assert.Equal(t, manifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest))
@ -534,7 +533,7 @@ func TestPackageContainer(t *testing.T) {
assert.Len(t, pd.Files, 3) assert.Len(t, pd.Files, 3)
for _, pfd := range pd.Files { for _, pfd := range pd.Files {
if pfd.File.Name == container_model.ManifestFilename { if pfd.File.Name == container_module.ManifestFilename {
assert.True(t, pfd.File.IsLead) assert.True(t, pfd.File.IsLead)
assert.Equal(t, oci.MediaTypeImageManifest, pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, oci.MediaTypeImageManifest, pfd.Properties.GetByName(container_module.PropertyMediaType))
assert.Equal(t, untaggedManifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) assert.Equal(t, untaggedManifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest))

View File

@ -46,21 +46,30 @@ func TestPackageNuGet(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
type FeedEntryProperties struct { type FeedEntryProperties struct {
Version string `xml:"Version"`
NormalizedVersion string `xml:"NormalizedVersion"`
Authors string `xml:"Authors"` Authors string `xml:"Authors"`
Copyright string `xml:"Copyright,omitempty"`
Created nuget.TypedValue[time.Time] `xml:"Created"`
Dependencies string `xml:"Dependencies"` Dependencies string `xml:"Dependencies"`
Description string `xml:"Description"` Description string `xml:"Description"`
VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"` DevelopmentDependency nuget.TypedValue[bool] `xml:"DevelopmentDependency"`
DownloadCount nuget.TypedValue[int64] `xml:"DownloadCount"` DownloadCount nuget.TypedValue[int64] `xml:"DownloadCount"`
PackageSize nuget.TypedValue[int64] `xml:"PackageSize"` ID string `xml:"Id"`
Created nuget.TypedValue[time.Time] `xml:"Created"` IconURL string `xml:"IconUrl,omitempty"`
Language string `xml:"Language,omitempty"`
LastUpdated nuget.TypedValue[time.Time] `xml:"LastUpdated"` LastUpdated nuget.TypedValue[time.Time] `xml:"LastUpdated"`
Published nuget.TypedValue[time.Time] `xml:"Published"` LicenseURL string `xml:"LicenseUrl,omitempty"`
MinClientVersion string `xml:"MinClientVersion,omitempty"`
NormalizedVersion string `xml:"NormalizedVersion"`
Owners string `xml:"Owners,omitempty"`
PackageSize nuget.TypedValue[int64] `xml:"PackageSize"`
ProjectURL string `xml:"ProjectUrl,omitempty"` ProjectURL string `xml:"ProjectUrl,omitempty"`
Published nuget.TypedValue[time.Time] `xml:"Published"`
ReleaseNotes string `xml:"ReleaseNotes,omitempty"` ReleaseNotes string `xml:"ReleaseNotes,omitempty"`
RequireLicenseAcceptance nuget.TypedValue[bool] `xml:"RequireLicenseAcceptance"` RequireLicenseAcceptance nuget.TypedValue[bool] `xml:"RequireLicenseAcceptance"`
Tags string `xml:"Tags,omitempty"`
Title string `xml:"Title"` Title string `xml:"Title"`
Version string `xml:"Version"`
VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"`
} }
type FeedEntry struct { type FeedEntry struct {
@ -86,28 +95,54 @@ func TestPackageNuGet(t *testing.T) {
readToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadPackage) readToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadPackage)
badToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadNotification) badToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadNotification)
packageName := "test.package" packageName := "test.package" // id
packageID := packageName
packageVersion := "1.0.3" packageVersion := "1.0.3"
packageAuthors := "KN4CK3R" packageAuthors := "KN4CK3R"
packageDescription := "Gitea Test Package" packageDescription := "Gitea Test Package"
symbolFilename := "test.pdb" symbolFilename := "test.pdb"
symbolID := "d910bb6948bd4c6cb40155bcf52c3c94" symbolID := "d910bb6948bd4c6cb40155bcf52c3c94"
packageCopyright := "Package Copyright"
packageIconURL := "https://gitea.io/favicon.png"
packageLanguage := "Package Language"
packageLicenseURL := "https://gitea.io/license"
packageMinClientVersion := "1.0.0.0"
packageOwners := "Package Owners"
packageProjectURL := "https://gitea.io"
packageReleaseNotes := "Package Release Notes"
packageTags := "tag_1 tag_2 tag_3"
packageTitle := "Package Title"
packageDevelopmentDependency := true
packageRequireLicenseAcceptance := true
createNuspec := func(id, version string) string { createNuspec := func(id, version string) string {
return `<?xml version="1.0" encoding="utf-8"?> return `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata> <metadata minClientVersion="` + packageMinClientVersion + `">
<id>` + id + `</id> <authors>` + packageAuthors + `</authors>
<version>` + version + `</version> <copyright>` + packageCopyright + `</copyright>
<authors>` + packageAuthors + `</authors> <description>` + packageDescription + `</description>
<description>` + packageDescription + `</description> <developmentDependency>true</developmentDependency>
<dependencies> <iconUrl>` + packageIconURL + `</iconUrl>
<group targetFramework=".NETStandard2.0"> <id>` + id + `</id>
<dependency id="Microsoft.CSharp" version="4.5.0" /> <language>` + packageLanguage + `</language>
</group> <licenseUrl>` + packageLicenseURL + `</licenseUrl>
</dependencies> <owners>` + packageOwners + `</owners>
</metadata> <projectUrl>` + packageProjectURL + `</projectUrl>
</package>` <releaseNotes>` + packageReleaseNotes + `</releaseNotes>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<tags>` + packageTags + `</tags>
<title>` + packageTitle + `</title>
<version>` + version + `</version>
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="Microsoft.CSharp" version="4.5.0" />
</group>
</dependencies>
</metadata>
</package>`
} }
createPackage := func(id, version string) *bytes.Buffer { createPackage := func(id, version string) *bytes.Buffer {
@ -393,7 +428,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int64(412), pb.Size) assert.Equal(t, int64(610), pb.Size)
case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion): case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion):
assert.False(t, pf.IsLead) assert.False(t, pf.IsLead)
@ -405,7 +440,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int64(427), pb.Size) assert.Equal(t, int64(996), pb.Size)
case symbolFilename: case symbolFilename:
assert.False(t, pf.IsLead) assert.False(t, pf.IsLead)
@ -736,10 +771,24 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
var result FeedEntry var result FeedEntry
decodeXML(t, resp, &result) decodeXML(t, resp, &result)
assert.Equal(t, packageName, result.Properties.Title)
assert.Equal(t, packageVersion, result.Properties.Version)
assert.Equal(t, packageAuthors, result.Properties.Authors) assert.Equal(t, packageAuthors, result.Properties.Authors)
assert.Equal(t, packageDescription, result.Properties.Description) assert.Equal(t, packageDescription, result.Properties.Description)
assert.Equal(t, packageID, result.Properties.ID)
assert.Equal(t, packageVersion, result.Properties.Version)
assert.Equal(t, packageCopyright, result.Properties.Copyright)
assert.Equal(t, packageDevelopmentDependency, result.Properties.DevelopmentDependency.Value)
assert.Equal(t, packageIconURL, result.Properties.IconURL)
assert.Equal(t, packageLanguage, result.Properties.Language)
assert.Equal(t, packageLicenseURL, result.Properties.LicenseURL)
assert.Equal(t, packageMinClientVersion, result.Properties.MinClientVersion)
assert.Equal(t, packageOwners, result.Properties.Owners)
assert.Equal(t, packageProjectURL, result.Properties.ProjectURL)
assert.Equal(t, packageReleaseNotes, result.Properties.ReleaseNotes)
assert.Equal(t, packageRequireLicenseAcceptance, result.Properties.RequireLicenseAcceptance.Value)
assert.Equal(t, packageTags, result.Properties.Tags)
assert.Equal(t, packageTitle, result.Properties.Title)
assert.Equal(t, "Microsoft.CSharp:4.5.0:.NETStandard2.0", result.Properties.Dependencies) assert.Equal(t, "Microsoft.CSharp:4.5.0:.NETStandard2.0", result.Properties.Dependencies)
}) })

View File

@ -15,9 +15,9 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -538,7 +538,7 @@ func TestPackageCleanup(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, pbs) assert.NotEmpty(t, pbs)
_, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion) _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion)
assert.NoError(t, err) assert.NoError(t, err)
err = packages_cleanup_service.CleanupTask(db.DefaultContext, duration) err = packages_cleanup_service.CleanupTask(db.DefaultContext, duration)
@ -548,7 +548,7 @@ func TestPackageCleanup(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, pbs) assert.Empty(t, pbs)
_, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion) _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion)
assert.ErrorIs(t, err, packages_model.ErrPackageNotExist) assert.ErrorIs(t, err, packages_model.ErrPackageNotExist)
}) })