diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 5e3cbac8f9..4122f632ff 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -588,6 +588,8 @@ func CommonRoutes() *web.Route { r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease) r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification) r.Get("/gems/{filename}", rubygems.DownloadPackageFile) + r.Get("/info/{packagename}", rubygems.GetPackageInfo) + r.Get("/versions", rubygems.GetAllPackagesVersions) r.Group("/api/v1/gems", func() { r.Post("/", rubygems.UploadPackageFile) r.Delete("/yank", rubygems.DeletePackage) diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go index ba5f4de080..0a08378b47 100644 --- a/routers/api/packages/rubygems/rubygems.go +++ b/routers/api/packages/rubygems/rubygems.go @@ -6,6 +6,7 @@ package rubygems import ( "compress/gzip" "compress/zlib" + "crypto/md5" "errors" "fmt" "io" @@ -227,12 +228,7 @@ func UploadPackageFile(ctx *context.Context) { return } - var filename string - if rp.Metadata.Platform == "" || rp.Metadata.Platform == "ruby" { - filename = strings.ToLower(fmt.Sprintf("%s-%s.gem", rp.Name, rp.Version)) - } else { - filename = strings.ToLower(fmt.Sprintf("%s-%s-%s.gem", rp.Name, rp.Version, rp.Metadata.Platform)) - } + filename := makeGemFullFileName(rp.Name, rp.Version, rp.Metadata.Platform) _, _, err = packages_service.CreatePackageAndAddFile( ctx, @@ -300,6 +296,136 @@ func DeletePackage(ctx *context.Context) { } } +// GetPackageInfo returns a custom text based format for the single rubygem with a line for each version of the rubygem +// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/ +func GetPackageInfo(ctx *context.Context) { + packageName := ctx.Params("packagename") + versions, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, packageName) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if len(versions) == 0 { + apiError(ctx, http.StatusNotFound, nil) + return + } + infoContent, err := makePackageInfo(ctx, versions) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + ctx.PlainText(http.StatusOK, infoContent) +} + +// 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/ +func GetAllPackagesVersions(ctx *context.Context) { + packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + out := &strings.Builder{} + out.WriteString("---\n") + for _, pkg := range packages { + versions, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, pkg.Name) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if len(versions) == 0 { + continue + } + + info, err := makePackageInfo(ctx, versions) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + // format: RUBYGEM [-]VERSION_PLATFORM[,VERSION_PLATFORM],...] MD5 + _, _ = fmt.Fprintf(out, "%s ", pkg.Name) + for i, v := range versions { + sep := util.Iif(i == len(versions)-1, "", ",") + _, _ = fmt.Fprintf(out, "%s%s", v.Version, sep) + } + _, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info))) + } + + ctx.PlainText(http.StatusOK, out.String()) +} + +func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) { + out.WriteString(prefix) + if len(reqs) == 0 { + reqs = []rubygems_module.VersionRequirement{{Restriction: ">=", Version: "0"}} + } + for i, req := range reqs { + sep := util.Iif(i == 0, "", "&") + _, _ = fmt.Fprintf(out, "%s%s %s", sep, req.Restriction, req.Version) + } +} + +func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion) (string, error) { + // format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...] + // DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT] + // REQUIREMENT: KEY:VALUE (always contains "checksum") + pd, err := packages_model.GetPackageDescriptor(ctx, version) + if err != nil { + return "", err + } + + metadata := pd.Metadata.(*rubygems_module.Metadata) + fullFilename := makeGemFullFileName(pd.Package.Name, version.Version, metadata.Platform) + file, err := packages_model.GetFileForVersionByName(ctx, version.ID, fullFilename, "") + if err != nil { + return "", err + } + blob, err := packages_model.GetBlobByID(ctx, file.BlobID) + if err != nil { + return "", err + } + + buf := &strings.Builder{} + buf.WriteString(version.Version) + buf.WriteByte(' ') + for i, dep := range metadata.RuntimeDependencies { + sep := util.Iif(i == 0, "", ",") + writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf) + } + _, _ = fmt.Fprintf(buf, "|checksum:%s", blob.HashSHA256) + if len(metadata.RequiredRubyVersion) != 0 { + writePackageVersionRequirements(",ruby:", metadata.RequiredRubyVersion, buf) + } + if len(metadata.RequiredRubygemsVersion) != 0 { + writePackageVersionRequirements(",rubygems:", metadata.RequiredRubygemsVersion, buf) + } + return buf.String(), nil +} + +func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion) (string, error) { + ret := "---\n" + for _, v := range versions { + dep, err := makePackageVersionDependency(ctx, v) + if err != nil { + return "", err + } + ret += dep + "\n" + } + return ret, nil +} + +func makeGemFullFileName(gemName, version, platform string) string { + var basename string + if platform == "" || platform == "ruby" { + basename = fmt.Sprintf("%s-%s", gemName, version) + } else { + basename = fmt.Sprintf("%s-%s-%s", gemName, version, platform) + } + return strings.ToLower(basename) + ".gem" +} + func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) { pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ OwnerID: ctx.Package.Owner.ID, diff --git a/tests/integration/api_packages_rubygems_test.go b/tests/integration/api_packages_rubygems_test.go index 5670731c49..fe9283df4d 100644 --- a/tests/integration/api_packages_rubygems_test.go +++ b/tests/integration/api_packages_rubygems_test.go @@ -4,7 +4,11 @@ package integration import ( + "archive/tar" "bytes" + "compress/gzip" + "crypto/sha256" + "crypto/sha512" "encoding/base64" "fmt" "mime/multipart" @@ -21,101 +25,167 @@ import ( "github.com/stretchr/testify/assert" ) +type tarFile struct { + Name string + Data []byte +} + +func makeArchiveFileTar(files []*tarFile) []byte { + buf := new(bytes.Buffer) + tarWriter := tar.NewWriter(buf) + for _, file := range files { + _ = tarWriter.WriteHeader(&tar.Header{ + Typeflag: tar.TypeReg, + Name: file.Name, + Mode: 0o644, + Size: int64(len(file.Data)), + }) + _, _ = tarWriter.Write(file.Data) + } + _ = tarWriter.Close() + return buf.Bytes() +} + +func makeArchiveFileGz(data []byte) []byte { + buf := new(bytes.Buffer) + gzWriter, _ := gzip.NewWriterLevel(buf, gzip.NoCompression) + _, _ = gzWriter.Write(data) + _ = gzWriter.Close() + return buf.Bytes() +} + +func makeRubyGem(name, version string) []byte { + metadataContent := fmt.Sprintf(`--- !ruby/object:Gem::Specification +name: %s +version: !ruby/object:Gem::Version + version: %s +platform: ruby +authors: +- Gitea +autorequire: +bindir: bin +cert_chain: [] +date: 2021-08-23 00:00:00.000000000 Z +dependencies: +- !ruby/object:Gem::Dependency + name: runtime-dep + requirement: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 1.2.0 + - - "<" + - !ruby/object:Gem::Version + version: '2.0' + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 1.2.0 + - - "<" + - !ruby/object:Gem::Version + version: '2.0' +- !ruby/object:Gem::Dependency + name: dev-dep + requirement: !ruby/object:Gem::Requirement + requirements: + - - "~>" + - !ruby/object:Gem::Version + version: '5.2' + type: :development + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - "~>" + - !ruby/object:Gem::Version + version: '5.2' +description: RubyGems package test +email: rubygems@gitea.io +executables: [] +extensions: [] +extra_rdoc_files: [] +files: +- lib/gitea.rb +homepage: https://gitea.io/ +licenses: +- MIT +metadata: {} +post_install_message: +rdoc_options: [] +require_paths: +- lib +required_ruby_version: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 2.3.0 +required_rubygems_version: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: '1.0' +requirements: [] +rubyforge_project: +rubygems_version: 2.7.6.2 +signing_key: +specification_version: 4 +summary: Gitea package +test_files: [] +`, name, version) + + metadataGz := makeArchiveFileGz([]byte(metadataContent)) + dataTarGz := makeArchiveFileGz(makeArchiveFileTar([]*tarFile{ + { + Name: "lib/gitea.rb", + Data: []byte("class Gitea\nend"), + }, + })) + + checksumsYaml := fmt.Sprintf(`--- +SHA256: + metadata.gz: %x + data.tar.gz: %x +SHA512: + metadata.gz: %x + data.tar.gz: %x +`, sha256.Sum256(metadataGz), sha256.Sum256(dataTarGz), sha512.Sum512(metadataGz), sha512.Sum512(dataTarGz)) + + files := []*tarFile{ + { + Name: "data.tar.gz", + Data: dataTarGz, + }, + { + Name: "metadata.gz", + Data: metadataGz, + }, + { + Name: "checksums.yaml.gz", + Data: makeArchiveFileGz([]byte(checksumsYaml)), + }, + } + return makeArchiveFileTar(files) +} + func TestPackageRubyGems(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - packageName := "gitea" - packageVersion := "1.0.5" - packageFilename := "gitea-1.0.5.gem" + testGemName := "gitea" + testGemVersion := "1.0.5" + testGemContent := makeRubyGem(testGemName, testGemVersion) + testGemContentChecksum := fmt.Sprintf("%x", sha256.Sum256(testGemContent)) - gemContent, _ := base64.StdEncoding.DecodeString(`bWV0YWRhdGEuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAw -MAAwMDAwMDAwADAwMDAwMDAxMDQxADE0MTEwNzcyMzY2ADAxMzQ0MQAgMAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAd2hlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAw -MDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf -iwgA9vQjYQID1VVNb9QwEL37V5he9pRsmlJAFlQckCoOXAriQIUix5nNmsYf2JOqKwS/nYmz2d3Q -qqCCKpFdadfjmfdm5nmcLMv4k9DXm6Wrv4BCcQ5GiPcelF5pJVE7y6w0IHirESS7hhDJJu4I+jhu -Mc53Tsd5kZ8y30lcuWAEH2KY7HHtQhQs4+cJkwwuwNdeB6JhtbaNDoLTL1MQsFJrqQnr8jNrJJJH -WZTHWfEiK094UYj0zYvp4Z9YAx5sA1ZpSCS3M30zeWwo2bG60FvUBjIKJts2GwMW76r0Yr9NzjN3 -YhwsGX2Ozl4dpcWwvK9d43PQtDIv9igvHwSyIIwFmXHjqTqxLY8MPkCADmQk80p2EfZ6VbM6/ue6 -/1D0Bq7/qeA/zh6W82leHmhFWUHn/JbsEfT6q7QbiCpoj8l0QcEUFLmX6kq2wBEiMjBSd+Pwt7T5 -Ot0kuXYMbkD1KOuOBnWYb7hBsAP4bhlkFRqnqpWefMZ/pHCn6+WIFGq2dgY8EQq+RvRRLJcTyZJ1 -WhHqGPTu7QdmACXdJFLwb9+ZdxErbSPKrqsMxJhAWCJ1qaqRdtu6yktcT/STsamG0qp7rsa5EL/K -MBua30uw4ynzExqYWRJDfx8/kQWN3PwsDh2jYLr1W+pZcAmCs9splvnz/Flesqhbq21bXcGG/OLh -+2fv/JTF3hgZyCW9OaZjxoZjdnBGfgKpxZyJ1QYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGF0 -YS50YXIuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAwMAAw -MDAwMDAwADAwMDAwMDAwMjQyADE0MTEwNzcyMzY2ADAxMzM2MQAgMAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAd2hlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfiwgA -9vQjYQID7M/NCsMgDABgz32KrA/QxersK/Q17ExXIcyhlr7+HLv1sJ02KPhBCPk5JOyn881nsl2c -xI+gRDRaC3zbZ8RBCamlxGHolTFlX11kLwDFH6wp21hO2RYi/rD3bb5/7iCubFOCMbBtABzNkIjn -bvGlAnisOUE7EnOALUR2p7b06e6aV4iqqqrquJ4AAAD//wMA+sA/NQAIAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGNoZWNr -c3Vtcy55YW1sLmd6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwNDQ0ADAwMDAwMDAAMDAw -MDAwMAAwMDAwMDAwMDQ1MAAxNDExMDc3MjM2NgAwMTQ2MTIAIDAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDB3aGVlbAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAHdoZWVsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAwAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4sIAPb0 -I2ECA2WQOa4UQAxE8znFXGCQ21vbPyMj5wRuL0Qk6EecnmZCyKyy9FSvXq/X4/u3ryj68Xg+f/Zn -VHzGlx+/P57qvU4XxWalBKftSXOgCjNYkdRycrC5Axem+W4HqS12PNEv7836jF9vnlHxwSyxKY+y -go0cPblyHzkrZ4HF1GSVhe7mOOoasXNk2fnbUxb+19Pp9tobD/QlJKMX7y204PREh6nQ5hG9Alw6 -x4TnmtA+aekGfm6wAseog2LSgpR4Q7cYnAH3K4qAQa6A6JCC1gpuY7P+9YxE5SZ+j0eVGbaBTwBQ -iIqRUyyzLCoFCBdYNWxniapTavD97blXTzFvgoVoAsKBAtlU48cdaOmeZDpwV01OtcGwjscfeUrY -B9QBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`) + testAnotherGemName := "gitea-another" + testAnotherGemVersion := "0.99" root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name) - uploadFile := func(t *testing.T, expectedStatus int) { - req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(gemContent)). + uploadFile := func(t *testing.T, content []byte, expectedStatus int) { + req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(content)). AddBasicAuth(user.Name) MakeRequest(t, req, expectedStatus) } @@ -123,7 +193,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`) t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - uploadFile(t, http.StatusCreated) + uploadFile(t, testGemContent, http.StatusCreated) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems) assert.NoError(t, err) @@ -133,34 +203,33 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`) assert.NoError(t, err) assert.NotNil(t, pd.SemVer) assert.IsType(t, &rubygems.Metadata{}, pd.Metadata) - assert.Equal(t, packageName, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) + assert.Equal(t, testGemName, pd.Package.Name) + assert.Equal(t, testGemVersion, pd.Version.Version) pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) assert.NoError(t, err) assert.Len(t, pfs, 1) - assert.Equal(t, packageFilename, pfs[0].Name) + assert.Equal(t, fmt.Sprintf("%s-%s.gem", testGemName, testGemVersion), pfs[0].Name) assert.True(t, pfs[0].IsLead) pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) assert.NoError(t, err) - assert.Equal(t, int64(4608), pb.Size) + assert.EqualValues(t, len(testGemContent), pb.Size) }) t.Run("UploadExists", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - - uploadFile(t, http.StatusConflict) + uploadFile(t, testGemContent, http.StatusConflict) }) t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/gems/%s", root, packageFilename)). + req := NewRequest(t, "GET", fmt.Sprintf("%s/gems/%s-%s.gem", root, testGemName, testGemVersion)). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, gemContent, resp.Body.Bytes()) + assert.Equal(t, testGemContent, resp.Body.Bytes()) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems) assert.NoError(t, err) @@ -171,7 +240,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`) t.Run("DownloadGemspec", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/quick/Marshal.4.8/%sspec.rz", root, packageFilename)). + req := NewRequest(t, "GET", fmt.Sprintf("%s/quick/Marshal.4.8/%s-%s.gemspec.rz", root, testGemName, testGemVersion)). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -206,22 +275,63 @@ gAAAAP//MS06Gw==`) enumeratePackages(t, "prerelease_specs.4.8.gz", b) }) - t.Run("Delete", func(t *testing.T) { + t.Run("UploadAnother", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + uploadFile(t, makeRubyGem(testAnotherGemName, testAnotherGemVersion), http.StatusCreated) + }) + + t.Run("PackageInfo", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + req := NewRequest(t, "GET", fmt.Sprintf("%s/info/%s", root, testGemName)).AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + expected := fmt.Sprintf(`--- +1.0.5 runtime-dep:>= 1.2.0&< 2.0|checksum:%s,ruby:>= 2.3.0,rubygems:>= 1.0 +`, testGemContentChecksum) + assert.Equal(t, expected, resp.Body.String()) + }) + + t.Run("Versions", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, `--- +gitea 1.0.5 08843c2dd0ea19910e6b056b98e38f1c +gitea-another 0.99 8b639e4048d282941485368ec42609be +`, resp.Body.String()) + }) + + deleteGemPackage := func(t *testing.T, packageName, packageVersion string) { body := bytes.Buffer{} writer := multipart.NewWriter(&body) - writer.WriteField("gem_name", packageName) - writer.WriteField("version", packageVersion) - writer.Close() - + _ = writer.WriteField("gem_name", packageName) + _ = writer.WriteField("version", packageVersion) + _ = writer.Close() req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body). SetHeader("Content-Type", writer.FormDataContentType()). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusOK) + } + t.Run("DeleteAll", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + deleteGemPackage(t, testGemName, testGemVersion) + deleteGemPackage(t, testAnotherGemName, testAnotherGemVersion) pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems) assert.NoError(t, err) assert.Empty(t, pvs) }) + + t.Run("PackageInfoAfterDelete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req := NewRequest(t, "GET", fmt.Sprintf("%s/info/%s", root, testGemName)).AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("VersionsAfterDelete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "---\n", resp.Body.String()) + }) }