Files
gitea/tests/integration/api_packages_composer_test.go
T
silverwind 052bc4b496 chore: adapt to go 1.27
Restore the toolchain pin renovate dropped. The go directive has to lose
its patch version for that: go removes a toolchain equal to it, and every
go command then refuses to run until go.mod is tidied.

Go 1.27 changes the flate encoder, so the sizes and checksums pinned by
package tests move with the compressed output. Hash the avatar test input
directly instead of a png, which has no bearing on what it asserts.

encoding/json v2 is no longer an experiment, so drop the v1 implementation
and GOEXPERIMENT=jsonv2 with it.

Assisted-by: Claude Code:Opus 5
2026-08-19 19:49:51 +02:00

328 lines
12 KiB
Go

// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"bytes"
"fmt"
"net/http"
neturl "net/url"
"testing"
"gitea.dev/models/packages"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
composer_module "gitea.dev/modules/packages/composer"
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
"gitea.dev/routers/api/packages/composer"
"gitea.dev/tests"
"github.com/stretchr/testify/assert"
)
func TestPackageComposer(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
otherUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31})
vendorName := "gitea"
projectName := "composer-package"
packageName := vendorName + "/" + projectName
packageVersion := "1.0.3"
packageDescription := "Package Description"
packageType := "composer-plugin"
packageAuthor := "Gitea Authors"
packageLicense := "MIT"
packageBin := "./bin/script"
content := test.WriteZipArchive(map[string]string{
"composer.json": `{
"name": "` + packageName + `",
"description": "` + packageDescription + `",
"type": "` + packageType + `",
"license": "` + packageLicense + `",
"authors": [
{
"name": "` + packageAuthor + `"
}
],
"bin": [
"` + packageBin + `"
]
}`,
}).Bytes()
url := fmt.Sprintf("%sapi/packages/%s/composer", setting.AppURL, user.Name)
t.Run("ServiceIndex", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", url+"/packages.json").
AddBasicAuth(user.Name)
resp := MakeRequest(t, req, http.StatusOK)
result := DecodeJSON(t, resp, &composer.ServiceIndexResponse{})
assert.Equal(t, url+"/search.json?q=%query%&type=%type%", result.SearchTemplate)
assert.Equal(t, url+"/p2/%package%.json", result.MetadataTemplate)
assert.Equal(t, url+"/list.json", result.PackageList)
})
t.Run("Upload", func(t *testing.T) {
t.Run("MissingVersion", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusBadRequest)
})
t.Run("Valid", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
uploadURL := url + "?version=" + packageVersion
req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeComposer)
assert.NoError(t, err)
assert.Len(t, pvs, 1)
pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0])
assert.NoError(t, err)
assert.NotNil(t, pd.SemVer)
assert.IsType(t, &composer_module.Metadata{}, pd.Metadata)
assert.Equal(t, packageName, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version)
pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID)
assert.NoError(t, err)
assert.Len(t, pfs, 1)
assert.Equal(t, fmt.Sprintf("%s-%s.%s.zip", vendorName, projectName, packageVersion), pfs[0].Name)
assert.True(t, pfs[0].IsLead)
pb, err := packages.GetBlobByID(t.Context(), pfs[0].BlobID)
assert.NoError(t, err)
assert.Equal(t, int64(len(content)), pb.Size)
req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(content)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusConflict)
})
})
t.Run("Download", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeComposer)
assert.NoError(t, err)
assert.Len(t, pvs, 1)
assert.Equal(t, int64(0), pvs[0].DownloadCount)
pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID)
assert.NoError(t, err)
assert.Len(t, pfs, 1)
req := NewRequest(t, "GET", fmt.Sprintf("%s/files/%s/%s/%s", url, neturl.PathEscape(packageName), neturl.PathEscape(pvs[0].LowerVersion), neturl.PathEscape(pfs[0].LowerName))).
AddBasicAuth(user.Name)
resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, content, resp.Body.Bytes())
pvs, err = packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeComposer)
assert.NoError(t, err)
assert.Len(t, pvs, 1)
assert.Equal(t, int64(1), pvs[0].DownloadCount)
})
t.Run("SearchService", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
cases := []struct {
Query string
Type string
Page int
PerPage int
ExpectedTotal int64
ExpectedResults int
}{
{"", "", 0, 0, 1, 1},
{"", "", 1, 1, 1, 1},
{"test", "", 1, 0, 0, 0},
{"gitea", "", 1, 1, 1, 1},
{"gitea", "", 2, 1, 1, 0},
{"", packageType, 1, 1, 1, 1},
{"gitea", packageType, 1, 1, 1, 1},
{"gitea", "dummy", 1, 1, 0, 0},
}
for i, c := range cases {
req := NewRequest(t, "GET", fmt.Sprintf("%s/search.json?q=%s&type=%s&page=%d&per_page=%d", url, c.Query, c.Type, c.Page, c.PerPage)).
AddBasicAuth(user.Name)
resp := MakeRequest(t, req, http.StatusOK)
result := DecodeJSON(t, resp, &composer.SearchResultResponse{})
assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i)
assert.Len(t, result.Results, c.ExpectedResults, "case %d: unexpected result count", i)
}
})
t.Run("EnumeratePackages", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", url+"/list.json").
AddBasicAuth(user.Name)
resp := MakeRequest(t, req, http.StatusOK)
result := DecodeJSON(t, resp, map[string][]string{})
assert.Contains(t, result, "packageNames")
names := result["packageNames"]
assert.Len(t, names, 1)
assert.Equal(t, packageName, names[0])
})
t.Run("PackageMetadata", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)).
AddBasicAuth(user.Name)
resp := MakeRequest(t, req, http.StatusOK)
result := DecodeJSON(t, resp, &composer.PackageMetadataResponse{})
assert.Contains(t, result.Packages, packageName)
pkgs := result.Packages[packageName]
assert.Len(t, pkgs, 1)
assert.Equal(t, packageName, pkgs[0].Name)
assert.Equal(t, packageVersion, pkgs[0].Version)
assert.Equal(t, packageType, pkgs[0].Type)
assert.Equal(t, packageDescription, pkgs[0].Description)
assert.Len(t, pkgs[0].Authors, 1)
assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name)
assert.Equal(t, "zip", pkgs[0].Dist.Type)
assert.Equal(t, "f2d7c9059b66874092336aac6b0e96c98b3916c6", pkgs[0].Dist.Checksum)
assert.Len(t, pkgs[0].Bin, 1)
assert.Equal(t, packageBin, pkgs[0].Bin[0])
// Test package linked to repository
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
userPkgs, err := packages.GetPackagesByType(t.Context(), user.ID, packages.TypeComposer)
assert.NoError(t, err)
assert.Len(t, userPkgs, 1)
assert.EqualValues(t, 0, userPkgs[0].RepoID)
err = packages.SetRepositoryLink(t.Context(), userPkgs[0].ID, repo1.ID)
assert.NoError(t, err)
req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)).
AddBasicAuth(user.Name)
resp = MakeRequest(t, req, http.StatusOK)
result = DecodeJSON(t, resp, &composer.PackageMetadataResponse{})
assert.Contains(t, result.Packages, packageName)
pkgs = result.Packages[packageName]
assert.Len(t, pkgs, 1)
assert.Equal(t, packageName, pkgs[0].Name)
assert.Equal(t, packageVersion, pkgs[0].Version)
assert.Equal(t, packageType, pkgs[0].Type)
assert.Equal(t, packageDescription, pkgs[0].Description)
assert.Len(t, pkgs[0].Authors, 1)
assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name)
assert.Equal(t, "zip", pkgs[0].Dist.Type)
assert.Equal(t, "f2d7c9059b66874092336aac6b0e96c98b3916c6", pkgs[0].Dist.Checksum)
assert.Len(t, pkgs[0].Bin, 1)
assert.Equal(t, packageBin, pkgs[0].Bin[0])
assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL)
assert.Equal(t, "git", pkgs[0].Source.Type)
assert.Equal(t, packageVersion, pkgs[0].Source.Reference)
// Private repository links remain visible to callers who can access the repository.
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
err = packages.SetRepositoryLink(t.Context(), userPkgs[0].ID, repo2.ID)
assert.NoError(t, err)
req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)).
AddBasicAuth(user.Name)
resp = MakeRequest(t, req, http.StatusOK)
result = DecodeJSON(t, resp, &composer.PackageMetadataResponse{})
pkgs = result.Packages[packageName]
assert.Len(t, pkgs, 1)
assert.Equal(t, repo2.HTMLURL(), pkgs[0].Source.URL)
assert.Equal(t, "git", pkgs[0].Source.Type)
assert.Equal(t, packageVersion, pkgs[0].Source.Reference)
// Callers without repository access still get the package metadata, but not the private source URL.
req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)).
AddBasicAuth(otherUser.Name)
resp = MakeRequest(t, req, http.StatusOK)
result = DecodeJSON(t, resp, &composer.PackageMetadataResponse{})
pkgs = result.Packages[packageName]
assert.Len(t, pkgs, 1)
assert.Empty(t, pkgs[0].Source.URL)
assert.Empty(t, pkgs[0].Source.Type)
assert.Empty(t, pkgs[0].Source.Reference)
})
t.Run("WebVisibilityBadge", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
listReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages", user.Name)).
AddBasicAuth(user.Name)
listResp := MakeRequest(t, listReq, http.StatusOK)
listDoc := NewHTMLParser(t, bytes.NewReader(listResp.Body.Bytes()))
assert.Equal(t, 0, listDoc.Find(".item-title .ui.basic.label").Length())
viewReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages/composer/%s/%s", user.Name, neturl.PathEscape(packageName), neturl.PathEscape(packageVersion))).
AddBasicAuth(user.Name)
viewResp := MakeRequest(t, viewReq, http.StatusOK)
viewDoc := NewHTMLParser(t, bytes.NewReader(viewResp.Body.Bytes()))
assert.Equal(t, 0, viewDoc.Find(".issue-title-header .ui.basic.label").Length())
privatePackageName := privateUser.Name + "/private-composer-package"
privatePackageVersion := "1.0.0"
privateContent := test.WriteZipArchive(map[string]string{
"composer.json": `{
"name": "` + privatePackageName + `",
"description": "Private Package",
"type": "` + packageType + `",
"license": "` + packageLicense + `",
"authors": [
{
"name": "` + packageAuthor + `"
}
]
}`,
}).Bytes()
privateUploadURL := fmt.Sprintf("%sapi/packages/%s/composer?version=%s", setting.AppURL, privateUser.Name, privatePackageVersion)
uploadReq := NewRequestWithBody(t, "PUT", privateUploadURL, bytes.NewReader(privateContent)).
AddBasicAuth(privateUser.Name)
MakeRequest(t, uploadReq, http.StatusCreated)
privateSession := loginUser(t, privateUser.Name)
privateListReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages", privateUser.Name))
privateListResp := privateSession.MakeRequest(t, privateListReq, http.StatusOK)
privateListDoc := NewHTMLParser(t, bytes.NewReader(privateListResp.Body.Bytes()))
assert.Equal(t, 1, privateListDoc.Find(".item-title .ui.basic.label").Length())
assert.Equal(t, "Private", privateListDoc.Find(".item-title .ui.basic.label").First().Text())
privateViewReq := NewRequest(t, "GET", fmt.Sprintf("/%s/-/packages/composer/%s/%s", privateUser.Name, neturl.PathEscape(privatePackageName), neturl.PathEscape(privatePackageVersion)))
privateViewResp := privateSession.MakeRequest(t, privateViewReq, http.StatusOK)
privateViewDoc := NewHTMLParser(t, bytes.NewReader(privateViewResp.Body.Bytes()))
assert.Equal(t, 1, privateViewDoc.Find(".issue-title-header .ui.basic.label").Length())
assert.Equal(t, "Private", privateViewDoc.Find(".issue-title-header .ui.basic.label").First().Text())
})
}