mirror of https://github.com/go-gitea/gitea
Add Go package registry (#24687)
Fixes #7608 This PR adds a Go package registry usable with the Go proxy protocol. ![grafik](https://github.com/go-gitea/gitea/assets/1666336/328feb5c-3df2-4f9d-8eae-fe3126d14c37)pull/24711/head^2
parent
53a00017bb
commit
5968c63a11
@ -0,0 +1,77 @@ |
||||
--- |
||||
date: "2023-05-10T00:00:00+00:00" |
||||
title: "Go Packages Repository" |
||||
slug: "go" |
||||
weight: 45 |
||||
draft: false |
||||
toc: false |
||||
menu: |
||||
sidebar: |
||||
parent: "packages" |
||||
name: "Go" |
||||
weight: 45 |
||||
identifier: "go" |
||||
--- |
||||
|
||||
# Go Packages Repository |
||||
|
||||
Publish Go packages for your user or organization. |
||||
|
||||
**Table of Contents** |
||||
|
||||
{{< toc >}} |
||||
|
||||
## Publish a package |
||||
|
||||
To publish a Go package perform a HTTP `PUT` operation with the package content in the request body. |
||||
You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first. |
||||
The package must follow the [documented structure](https://go.dev/ref/mod#zip-files). |
||||
|
||||
``` |
||||
PUT https://gitea.example.com/api/packages/{owner}/go/upload |
||||
``` |
||||
|
||||
| Parameter | Description | |
||||
| --------- | ----------- | |
||||
| `owner` | The owner of the package. | |
||||
|
||||
To authenticate to the package registry, you need to provide [custom HTTP headers or use HTTP Basic authentication]({{< relref "doc/development/api-usage.en-us.md#authentication" >}}): |
||||
|
||||
```shell |
||||
curl --user your_username:your_password_or_token \ |
||||
--upload-file path/to/file.zip \ |
||||
https://gitea.example.com/api/packages/testuser/go/upload |
||||
``` |
||||
|
||||
If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/development/api-usage.en-us.md#authentication" >}}) instead of the password. |
||||
|
||||
The server responds with the following HTTP Status codes. |
||||
|
||||
| HTTP Status Code | Meaning | |
||||
| ----------------- | ------- | |
||||
| `201 Created` | The package has been published. | |
||||
| `400 Bad Request` | The package is invalid. | |
||||
| `409 Conflict` | A package with the same name exist already. | |
||||
|
||||
## Install a package |
||||
|
||||
To install a Go package instruct Go to use the package registry as proxy: |
||||
|
||||
```shell |
||||
# use latest version |
||||
GOPROXY=https://gitea.example.com/api/packages/{owner}/go go install {package_name} |
||||
# or |
||||
GOPROXY=https://gitea.example.com/api/packages/{owner}/go go install {package_name}@latest |
||||
# use specific version |
||||
GOPROXY=https://gitea.example.com/api/packages/{owner}/go go install {package_name}@{package_version} |
||||
``` |
||||
|
||||
| Parameter | Description | |
||||
| ----------------- | ----------- | |
||||
| `owner` | The owner of the package. | |
||||
| `package_name` | The package name. | |
||||
| `package_version` | The package version. | |
||||
|
||||
If the owner of the packages is private you need to [provide credentials](https://go.dev/ref/mod#private-module-proxy-auth). |
||||
|
||||
More information about the `GOPROXY` environment variable and how to protect against data leaks can be found in [the documentation](https://go.dev/ref/mod#private-modules). |
@ -0,0 +1,94 @@ |
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package goproxy |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"fmt" |
||||
"io" |
||||
"path" |
||||
"strings" |
||||
|
||||
"code.gitea.io/gitea/modules/util" |
||||
) |
||||
|
||||
const ( |
||||
PropertyGoMod = "go.mod" |
||||
|
||||
maxGoModFileSize = 16 * 1024 * 1024 // https://go.dev/ref/mod#zip-path-size-constraints
|
||||
) |
||||
|
||||
var ( |
||||
ErrInvalidStructure = util.NewInvalidArgumentErrorf("package has invalid structure") |
||||
ErrGoModFileTooLarge = util.NewInvalidArgumentErrorf("go.mod file is too large") |
||||
) |
||||
|
||||
type Package struct { |
||||
Name string |
||||
Version string |
||||
GoMod string |
||||
} |
||||
|
||||
// ParsePackage parses the Go package file
|
||||
// https://go.dev/ref/mod#zip-files
|
||||
func ParsePackage(r io.ReaderAt, size int64) (*Package, error) { |
||||
archive, err := zip.NewReader(r, size) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var p *Package |
||||
|
||||
for _, file := range archive.File { |
||||
nameAndVersion := path.Dir(file.Name) |
||||
|
||||
parts := strings.SplitN(nameAndVersion, "@", 2) |
||||
if len(parts) != 2 { |
||||
continue |
||||
} |
||||
|
||||
versionParts := strings.SplitN(parts[1], "/", 2) |
||||
|
||||
if p == nil { |
||||
p = &Package{ |
||||
Name: strings.TrimSuffix(nameAndVersion, "@"+parts[1]), |
||||
Version: versionParts[0], |
||||
} |
||||
} |
||||
|
||||
if len(versionParts) > 1 { |
||||
// files are expected in the "root" folder
|
||||
continue |
||||
} |
||||
|
||||
if path.Base(file.Name) == "go.mod" { |
||||
if file.UncompressedSize64 > maxGoModFileSize { |
||||
return nil, ErrGoModFileTooLarge |
||||
} |
||||
|
||||
f, err := archive.Open(file.Name) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer f.Close() |
||||
|
||||
bytes, err := io.ReadAll(&io.LimitedReader{R: f, N: maxGoModFileSize}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
p.GoMod = string(bytes) |
||||
|
||||
return p, nil |
||||
} |
||||
} |
||||
|
||||
if p == nil { |
||||
return nil, ErrInvalidStructure |
||||
} |
||||
|
||||
p.GoMod = fmt.Sprintf("module %s", p.Name) |
||||
|
||||
return p, nil |
||||
} |
@ -0,0 +1,75 @@ |
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package goproxy |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"bytes" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
const ( |
||||
packageName = "gitea.com/go-gitea/gitea" |
||||
packageVersion = "v0.0.1" |
||||
) |
||||
|
||||
func TestParsePackage(t *testing.T) { |
||||
createArchive := func(files map[string][]byte) *bytes.Reader { |
||||
var buf bytes.Buffer |
||||
zw := zip.NewWriter(&buf) |
||||
for name, content := range files { |
||||
w, _ := zw.Create(name) |
||||
w.Write(content) |
||||
} |
||||
zw.Close() |
||||
return bytes.NewReader(buf.Bytes()) |
||||
} |
||||
|
||||
t.Run("EmptyPackage", func(t *testing.T) { |
||||
data := createArchive(nil) |
||||
|
||||
p, err := ParsePackage(data, int64(data.Len())) |
||||
assert.Nil(t, p) |
||||
assert.ErrorIs(t, err, ErrInvalidStructure) |
||||
}) |
||||
|
||||
t.Run("InvalidNameOrVersionStructure", func(t *testing.T) { |
||||
data := createArchive(map[string][]byte{ |
||||
packageName + "/" + packageVersion + "/go.mod": {}, |
||||
}) |
||||
|
||||
p, err := ParsePackage(data, int64(data.Len())) |
||||
assert.Nil(t, p) |
||||
assert.ErrorIs(t, err, ErrInvalidStructure) |
||||
}) |
||||
|
||||
t.Run("GoModFileInWrongDirectory", func(t *testing.T) { |
||||
data := createArchive(map[string][]byte{ |
||||
packageName + "@" + packageVersion + "/subdir/go.mod": {}, |
||||
}) |
||||
|
||||
p, err := ParsePackage(data, int64(data.Len())) |
||||
assert.NotNil(t, p) |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, packageName, p.Name) |
||||
assert.Equal(t, packageVersion, p.Version) |
||||
assert.Equal(t, "module gitea.com/go-gitea/gitea", p.GoMod) |
||||
}) |
||||
|
||||
t.Run("Valid", func(t *testing.T) { |
||||
data := createArchive(map[string][]byte{ |
||||
packageName + "@" + packageVersion + "/subdir/go.mod": []byte("invalid"), |
||||
packageName + "@" + packageVersion + "/go.mod": []byte("valid"), |
||||
}) |
||||
|
||||
p, err := ParsePackage(data, int64(data.Len())) |
||||
assert.NotNil(t, p) |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, packageName, p.Name) |
||||
assert.Equal(t, packageVersion, p.Version) |
||||
assert.Equal(t, "valid", p.GoMod) |
||||
}) |
||||
} |
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,226 @@ |
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package goproxy |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"sort" |
||||
"time" |
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages" |
||||
"code.gitea.io/gitea/modules/context" |
||||
packages_module "code.gitea.io/gitea/modules/packages" |
||||
goproxy_module "code.gitea.io/gitea/modules/packages/goproxy" |
||||
"code.gitea.io/gitea/modules/util" |
||||
"code.gitea.io/gitea/routers/api/packages/helper" |
||||
packages_service "code.gitea.io/gitea/services/packages" |
||||
) |
||||
|
||||
func apiError(ctx *context.Context, status int, obj interface{}) { |
||||
helper.LogAndProcessError(ctx, status, obj, func(message string) { |
||||
ctx.PlainText(status, message) |
||||
}) |
||||
} |
||||
|
||||
func EnumeratePackageVersions(ctx *context.Context) { |
||||
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeGo, ctx.Params("name")) |
||||
if err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
if len(pvs) == 0 { |
||||
apiError(ctx, http.StatusNotFound, err) |
||||
return |
||||
} |
||||
|
||||
sort.Slice(pvs, func(i, j int) bool { |
||||
return pvs[i].CreatedUnix < pvs[j].CreatedUnix |
||||
}) |
||||
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") |
||||
|
||||
for _, pv := range pvs { |
||||
fmt.Fprintln(ctx.Resp, pv.Version) |
||||
} |
||||
} |
||||
|
||||
func PackageVersionMetadata(ctx *context.Context) { |
||||
pv, err := resolvePackage(ctx, ctx.Package.Owner.ID, ctx.Params("name"), ctx.Params("version")) |
||||
if err != nil { |
||||
if errors.Is(err, util.ErrNotExist) { |
||||
apiError(ctx, http.StatusNotFound, err) |
||||
} else { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
} |
||||
return |
||||
} |
||||
|
||||
ctx.JSON(http.StatusOK, struct { |
||||
Version string `json:"Version"` |
||||
Time time.Time `json:"Time"` |
||||
}{ |
||||
Version: pv.Version, |
||||
Time: pv.CreatedUnix.AsLocalTime(), |
||||
}) |
||||
} |
||||
|
||||
func PackageVersionGoModContent(ctx *context.Context) { |
||||
pv, err := resolvePackage(ctx, ctx.Package.Owner.ID, ctx.Params("name"), ctx.Params("version")) |
||||
if err != nil { |
||||
if errors.Is(err, util.ErrNotExist) { |
||||
apiError(ctx, http.StatusNotFound, err) |
||||
} else { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
} |
||||
return |
||||
} |
||||
|
||||
pps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, goproxy_module.PropertyGoMod) |
||||
if err != nil || len(pps) != 1 { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
|
||||
ctx.PlainText(http.StatusOK, pps[0].Value) |
||||
} |
||||
|
||||
func DownloadPackageFile(ctx *context.Context) { |
||||
pv, err := resolvePackage(ctx, ctx.Package.Owner.ID, ctx.Params("name"), ctx.Params("version")) |
||||
if err != nil { |
||||
if errors.Is(err, util.ErrNotExist) { |
||||
apiError(ctx, http.StatusNotFound, err) |
||||
} else { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
} |
||||
return |
||||
} |
||||
|
||||
pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID) |
||||
if err != nil || len(pfs) != 1 { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
|
||||
s, _, err := packages_service.GetPackageFileStream(ctx, pfs[0]) |
||||
if err != nil { |
||||
if errors.Is(err, util.ErrNotExist) { |
||||
apiError(ctx, http.StatusNotFound, err) |
||||
} else { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
} |
||||
return |
||||
} |
||||
defer s.Close() |
||||
|
||||
ctx.ServeContent(s, &context.ServeHeaderOptions{ |
||||
Filename: pfs[0].Name, |
||||
LastModified: pfs[0].CreatedUnix.AsLocalTime(), |
||||
}) |
||||
} |
||||
|
||||
func resolvePackage(ctx *context.Context, ownerID int64, name, version string) (*packages_model.PackageVersion, error) { |
||||
var pv *packages_model.PackageVersion |
||||
|
||||
if version == "latest" { |
||||
pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ |
||||
OwnerID: ownerID, |
||||
Type: packages_model.TypeGo, |
||||
Name: packages_model.SearchValue{ |
||||
Value: name, |
||||
ExactMatch: true, |
||||
}, |
||||
IsInternal: util.OptionalBoolFalse, |
||||
Sort: packages_model.SortCreatedDesc, |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if len(pvs) != 1 { |
||||
return nil, packages_model.ErrPackageNotExist |
||||
} |
||||
|
||||
pv = pvs[0] |
||||
} else { |
||||
var err error |
||||
pv, err = packages_model.GetVersionByNameAndVersion(ctx, ownerID, packages_model.TypeGo, name, version) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
return pv, nil |
||||
} |
||||
|
||||
func UploadPackage(ctx *context.Context) { |
||||
upload, close, err := ctx.UploadStream() |
||||
if err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
if close { |
||||
defer upload.Close() |
||||
} |
||||
|
||||
buf, err := packages_module.CreateHashedBufferFromReader(upload) |
||||
if err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
defer buf.Close() |
||||
|
||||
pck, err := goproxy_module.ParsePackage(buf, buf.Size()) |
||||
if err != nil { |
||||
if errors.Is(err, util.ErrInvalidArgument) { |
||||
apiError(ctx, http.StatusBadRequest, err) |
||||
} else { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
} |
||||
return |
||||
} |
||||
|
||||
if _, err := buf.Seek(0, io.SeekStart); err != nil { |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
return |
||||
} |
||||
|
||||
_, _, err = packages_service.CreatePackageAndAddFile( |
||||
&packages_service.PackageCreationInfo{ |
||||
PackageInfo: packages_service.PackageInfo{ |
||||
Owner: ctx.Package.Owner, |
||||
PackageType: packages_model.TypeGo, |
||||
Name: pck.Name, |
||||
Version: pck.Version, |
||||
}, |
||||
Creator: ctx.Doer, |
||||
VersionProperties: map[string]string{ |
||||
goproxy_module.PropertyGoMod: pck.GoMod, |
||||
}, |
||||
}, |
||||
&packages_service.PackageFileCreationInfo{ |
||||
PackageFileInfo: packages_service.PackageFileInfo{ |
||||
Filename: fmt.Sprintf("%v.zip", pck.Version), |
||||
}, |
||||
Creator: ctx.Doer, |
||||
Data: buf, |
||||
IsLead: true, |
||||
}, |
||||
) |
||||
if err != nil { |
||||
switch err { |
||||
case packages_model.ErrDuplicatePackageVersion: |
||||
apiError(ctx, http.StatusConflict, err) |
||||
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: |
||||
apiError(ctx, http.StatusForbidden, err) |
||||
default: |
||||
apiError(ctx, http.StatusInternalServerError, err) |
||||
} |
||||
return |
||||
} |
||||
|
||||
ctx.Status(http.StatusCreated) |
||||
} |
@ -0,0 +1,14 @@ |
||||
{{if eq .PackageDescriptor.Package.Type "go"}} |
||||
<h4 class="ui top attached header">{{.locale.Tr "packages.installation"}}</h4> |
||||
<div class="ui attached segment"> |
||||
<div class="ui form"> |
||||
<div class="field"> |
||||
<label>{{svg "octicon-terminal"}} {{.locale.Tr "packages.go.install"}}</label> |
||||
<div class="markup"><pre class="code-block"><code>GOPROXY=<gitea-origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/go"></gitea-origin-url> go install {{$.PackageDescriptor.Package.Name}}@{{$.PackageDescriptor.Version.Version}}</code></pre></div> |
||||
</div> |
||||
<div class="field"> |
||||
<label>{{.locale.Tr "packages.go.documentation" "https://docs.gitea.io/en-us/usage/packages/go" | Safe}}</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{{end}} |
@ -0,0 +1,166 @@ |
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"bytes" |
||||
"fmt" |
||||
"net/http" |
||||
"testing" |
||||
"time" |
||||
|
||||
"code.gitea.io/gitea/models/db" |
||||
"code.gitea.io/gitea/models/packages" |
||||
"code.gitea.io/gitea/models/unittest" |
||||
user_model "code.gitea.io/gitea/models/user" |
||||
"code.gitea.io/gitea/tests" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestPackageGo(t *testing.T) { |
||||
defer tests.PrepareTestEnv(t)() |
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) |
||||
|
||||
packageName := "gitea.com/go-gitea/gitea" |
||||
packageVersion := "v0.0.1" |
||||
packageVersion2 := "v0.0.2" |
||||
goModContent := `module "gitea.com/go-gitea/gitea"` |
||||
|
||||
createArchive := func(files map[string][]byte) []byte { |
||||
var buf bytes.Buffer |
||||
zw := zip.NewWriter(&buf) |
||||
for name, content := range files { |
||||
w, _ := zw.Create(name) |
||||
w.Write(content) |
||||
} |
||||
zw.Close() |
||||
return buf.Bytes() |
||||
} |
||||
|
||||
url := fmt.Sprintf("/api/packages/%s/go", user.Name) |
||||
|
||||
t.Run("Upload", func(t *testing.T) { |
||||
defer tests.PrintCurrentTest(t)() |
||||
|
||||
content := createArchive(nil) |
||||
|
||||
req := NewRequestWithBody(t, "PUT", url+"/upload", bytes.NewReader(content)) |
||||
MakeRequest(t, req, http.StatusUnauthorized) |
||||
|
||||
req = NewRequestWithBody(t, "PUT", url+"/upload", bytes.NewReader(content)) |
||||
AddBasicAuthHeader(req, user.Name) |
||||
MakeRequest(t, req, http.StatusBadRequest) |
||||
|
||||
content = createArchive(map[string][]byte{ |
||||
packageName + "@" + packageVersion + "/go.mod": []byte(goModContent), |
||||
}) |
||||
|
||||
req = NewRequestWithBody(t, "PUT", url+"/upload", bytes.NewReader(content)) |
||||
AddBasicAuthHeader(req, user.Name) |
||||
MakeRequest(t, req, http.StatusCreated) |
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeGo) |
||||
assert.NoError(t, err) |
||||
assert.Len(t, pvs, 1) |
||||
|
||||
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) |
||||
assert.NoError(t, err) |
||||
assert.Nil(t, pd.Metadata) |
||||
assert.Equal(t, packageName, pd.Package.Name) |
||||
assert.Equal(t, packageVersion, pd.Version.Version) |
||||
|
||||
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) |
||||
assert.NoError(t, err) |
||||
assert.Len(t, pfs, 1) |
||||
assert.Equal(t, packageVersion+".zip", 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(len(content)), pb.Size) |
||||
|
||||
req = NewRequestWithBody(t, "PUT", url+"/upload", bytes.NewReader(content)) |
||||
AddBasicAuthHeader(req, user.Name) |
||||
MakeRequest(t, req, http.StatusConflict) |
||||
|
||||
time.Sleep(time.Second) |
||||
|
||||
content = createArchive(map[string][]byte{ |
||||
packageName + "@" + packageVersion2 + "/go.mod": []byte(goModContent), |
||||
}) |
||||
|
||||
req = NewRequestWithBody(t, "PUT", url+"/upload", bytes.NewReader(content)) |
||||
AddBasicAuthHeader(req, user.Name) |
||||
MakeRequest(t, req, http.StatusCreated) |
||||
}) |
||||
|
||||
t.Run("List", func(t *testing.T) { |
||||
defer tests.PrintCurrentTest(t)() |
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/list", url, packageName)) |
||||
resp := MakeRequest(t, req, http.StatusOK) |
||||
|
||||
assert.Equal(t, packageVersion+"\n"+packageVersion2+"\n", resp.Body.String()) |
||||
}) |
||||
|
||||
t.Run("Info", func(t *testing.T) { |
||||
defer tests.PrintCurrentTest(t)() |
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.info", url, packageName, packageVersion)) |
||||
resp := MakeRequest(t, req, http.StatusOK) |
||||
|
||||
type Info struct { |
||||
Version string `json:"Version"` |
||||
Time time.Time `json:"Time"` |
||||
} |
||||
|
||||
info := &Info{} |
||||
DecodeJSON(t, resp, &info) |
||||
|
||||
assert.Equal(t, packageVersion, info.Version) |
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/latest.info", url, packageName)) |
||||
resp = MakeRequest(t, req, http.StatusOK) |
||||
|
||||
info = &Info{} |
||||
DecodeJSON(t, resp, &info) |
||||
|
||||
assert.Equal(t, packageVersion2, info.Version) |
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@latest", url, packageName)) |
||||
resp = MakeRequest(t, req, http.StatusOK) |
||||
|
||||
info = &Info{} |
||||
DecodeJSON(t, resp, &info) |
||||
|
||||
assert.Equal(t, packageVersion2, info.Version) |
||||
}) |
||||
|
||||
t.Run("GoMod", func(t *testing.T) { |
||||
defer tests.PrintCurrentTest(t)() |
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.mod", url, packageName, packageVersion)) |
||||
resp := MakeRequest(t, req, http.StatusOK) |
||||
|
||||
assert.Equal(t, goModContent, resp.Body.String()) |
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/latest.mod", url, packageName)) |
||||
resp = MakeRequest(t, req, http.StatusOK) |
||||
|
||||
assert.Equal(t, goModContent, resp.Body.String()) |
||||
}) |
||||
|
||||
t.Run("Download", func(t *testing.T) { |
||||
defer tests.PrintCurrentTest(t)() |
||||
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.zip", url, packageName, packageVersion)) |
||||
MakeRequest(t, req, http.StatusOK) |
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/latest.zip", url, packageName)) |
||||
MakeRequest(t, req, http.StatusOK) |
||||
}) |
||||
} |
After Width: | Height: | Size: 1.7 KiB |
Loading…
Reference in new issue