code.gitea.io/gitea@v1.21.7/tests/integration/api_packages_rpm_test.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"bytes"
     8  	"compress/gzip"
     9  	"encoding/base64"
    10  	"encoding/xml"
    11  	"fmt"
    12  	"io"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"testing"
    16  
    17  	"code.gitea.io/gitea/models/db"
    18  	"code.gitea.io/gitea/models/packages"
    19  	"code.gitea.io/gitea/models/unittest"
    20  	user_model "code.gitea.io/gitea/models/user"
    21  	rpm_module "code.gitea.io/gitea/modules/packages/rpm"
    22  	"code.gitea.io/gitea/modules/setting"
    23  	"code.gitea.io/gitea/tests"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  )
    27  
    28  func TestPackageRpm(t *testing.T) {
    29  	defer tests.PrepareTestEnv(t)()
    30  
    31  	packageName := "gitea-test"
    32  	packageVersion := "1.0.2-1"
    33  	packageArchitecture := "x86_64"
    34  
    35  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
    36  
    37  	base64RpmPackageContent := `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`
    65  	rpmPackageContent, err := base64.StdEncoding.DecodeString(base64RpmPackageContent)
    66  	assert.NoError(t, err)
    67  
    68  	zr, err := gzip.NewReader(bytes.NewReader(rpmPackageContent))
    69  	assert.NoError(t, err)
    70  
    71  	content, err := io.ReadAll(zr)
    72  	assert.NoError(t, err)
    73  
    74  	rootURL := fmt.Sprintf("/api/packages/%s/rpm", user.Name)
    75  
    76  	t.Run("RepositoryConfig", func(t *testing.T) {
    77  		defer tests.PrintCurrentTest(t)()
    78  
    79  		req := NewRequest(t, "GET", rootURL+".repo")
    80  		resp := MakeRequest(t, req, http.StatusOK)
    81  
    82  		expected := fmt.Sprintf(`[gitea-%s]
    83  name=%s - %s
    84  baseurl=%sapi/packages/%s/rpm
    85  enabled=1
    86  gpgcheck=1
    87  gpgkey=%sapi/packages/%s/rpm/repository.key`, user.Name, user.Name, setting.AppName, setting.AppURL, user.Name, setting.AppURL, user.Name)
    88  
    89  		assert.Equal(t, expected, resp.Body.String())
    90  	})
    91  
    92  	t.Run("RepositoryKey", func(t *testing.T) {
    93  		defer tests.PrintCurrentTest(t)()
    94  
    95  		req := NewRequest(t, "GET", rootURL+"/repository.key")
    96  		resp := MakeRequest(t, req, http.StatusOK)
    97  
    98  		assert.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type"))
    99  		assert.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----")
   100  	})
   101  
   102  	t.Run("Upload", func(t *testing.T) {
   103  		url := rootURL + "/upload"
   104  
   105  		req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
   106  		MakeRequest(t, req, http.StatusUnauthorized)
   107  
   108  		req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
   109  		req = AddBasicAuthHeader(req, user.Name)
   110  		MakeRequest(t, req, http.StatusCreated)
   111  
   112  		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
   113  		assert.NoError(t, err)
   114  		assert.Len(t, pvs, 1)
   115  
   116  		pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
   117  		assert.NoError(t, err)
   118  		assert.Nil(t, pd.SemVer)
   119  		assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata)
   120  		assert.Equal(t, packageName, pd.Package.Name)
   121  		assert.Equal(t, packageVersion, pd.Version.Version)
   122  
   123  		pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
   124  		assert.NoError(t, err)
   125  		assert.Len(t, pfs, 1)
   126  		assert.Equal(t, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture), pfs[0].Name)
   127  		assert.True(t, pfs[0].IsLead)
   128  
   129  		pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
   130  		assert.NoError(t, err)
   131  		assert.Equal(t, int64(len(content)), pb.Size)
   132  
   133  		req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
   134  		req = AddBasicAuthHeader(req, user.Name)
   135  		MakeRequest(t, req, http.StatusConflict)
   136  	})
   137  
   138  	t.Run("Download", func(t *testing.T) {
   139  		defer tests.PrintCurrentTest(t)()
   140  
   141  		req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
   142  		resp := MakeRequest(t, req, http.StatusOK)
   143  
   144  		assert.Equal(t, content, resp.Body.Bytes())
   145  	})
   146  
   147  	t.Run("Repository", func(t *testing.T) {
   148  		defer tests.PrintCurrentTest(t)()
   149  
   150  		url := rootURL + "/repodata"
   151  
   152  		req := NewRequest(t, "HEAD", url+"/dummy.xml")
   153  		MakeRequest(t, req, http.StatusNotFound)
   154  
   155  		req = NewRequest(t, "GET", url+"/dummy.xml")
   156  		MakeRequest(t, req, http.StatusNotFound)
   157  
   158  		t.Run("repomd.xml", func(t *testing.T) {
   159  			defer tests.PrintCurrentTest(t)()
   160  
   161  			req = NewRequest(t, "HEAD", url+"/repomd.xml")
   162  			MakeRequest(t, req, http.StatusOK)
   163  
   164  			req = NewRequest(t, "GET", url+"/repomd.xml")
   165  			resp := MakeRequest(t, req, http.StatusOK)
   166  
   167  			type Repomd struct {
   168  				XMLName  xml.Name `xml:"repomd"`
   169  				Xmlns    string   `xml:"xmlns,attr"`
   170  				XmlnsRpm string   `xml:"xmlns:rpm,attr"`
   171  				Data     []struct {
   172  					Type     string `xml:"type,attr"`
   173  					Checksum struct {
   174  						Value string `xml:",chardata"`
   175  						Type  string `xml:"type,attr"`
   176  					} `xml:"checksum"`
   177  					OpenChecksum struct {
   178  						Value string `xml:",chardata"`
   179  						Type  string `xml:"type,attr"`
   180  					} `xml:"open-checksum"`
   181  					Location struct {
   182  						Href string `xml:"href,attr"`
   183  					} `xml:"location"`
   184  					Timestamp int64 `xml:"timestamp"`
   185  					Size      int64 `xml:"size"`
   186  					OpenSize  int64 `xml:"open-size"`
   187  				} `xml:"data"`
   188  			}
   189  
   190  			var result Repomd
   191  			decodeXML(t, resp, &result)
   192  
   193  			assert.Len(t, result.Data, 3)
   194  			for _, d := range result.Data {
   195  				assert.Equal(t, "sha256", d.Checksum.Type)
   196  				assert.NotEmpty(t, d.Checksum.Value)
   197  				assert.Equal(t, "sha256", d.OpenChecksum.Type)
   198  				assert.NotEmpty(t, d.OpenChecksum.Value)
   199  				assert.NotEqual(t, d.Checksum.Value, d.OpenChecksum.Value)
   200  				assert.Greater(t, d.OpenSize, d.Size)
   201  
   202  				switch d.Type {
   203  				case "primary":
   204  					assert.EqualValues(t, 718, d.Size)
   205  					assert.EqualValues(t, 1729, d.OpenSize)
   206  					assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href)
   207  				case "filelists":
   208  					assert.EqualValues(t, 257, d.Size)
   209  					assert.EqualValues(t, 326, d.OpenSize)
   210  					assert.Equal(t, "repodata/filelists.xml.gz", d.Location.Href)
   211  				case "other":
   212  					assert.EqualValues(t, 306, d.Size)
   213  					assert.EqualValues(t, 394, d.OpenSize)
   214  					assert.Equal(t, "repodata/other.xml.gz", d.Location.Href)
   215  				}
   216  			}
   217  		})
   218  
   219  		t.Run("repomd.xml.asc", func(t *testing.T) {
   220  			defer tests.PrintCurrentTest(t)()
   221  
   222  			req = NewRequest(t, "GET", url+"/repomd.xml.asc")
   223  			resp := MakeRequest(t, req, http.StatusOK)
   224  
   225  			assert.Contains(t, resp.Body.String(), "-----BEGIN PGP SIGNATURE-----")
   226  		})
   227  
   228  		decodeGzipXML := func(t testing.TB, resp *httptest.ResponseRecorder, v any) {
   229  			t.Helper()
   230  
   231  			zr, err := gzip.NewReader(resp.Body)
   232  			assert.NoError(t, err)
   233  
   234  			assert.NoError(t, xml.NewDecoder(zr).Decode(v))
   235  		}
   236  
   237  		t.Run("primary.xml.gz", func(t *testing.T) {
   238  			defer tests.PrintCurrentTest(t)()
   239  
   240  			req = NewRequest(t, "GET", url+"/primary.xml.gz")
   241  			resp := MakeRequest(t, req, http.StatusOK)
   242  
   243  			type EntryList struct {
   244  				Entries []*rpm_module.Entry `xml:"entry"`
   245  			}
   246  
   247  			type Metadata struct {
   248  				XMLName      xml.Name `xml:"metadata"`
   249  				Xmlns        string   `xml:"xmlns,attr"`
   250  				XmlnsRpm     string   `xml:"xmlns:rpm,attr"`
   251  				PackageCount int      `xml:"packages,attr"`
   252  				Packages     []struct {
   253  					XMLName      xml.Name `xml:"package"`
   254  					Type         string   `xml:"type,attr"`
   255  					Name         string   `xml:"name"`
   256  					Architecture string   `xml:"arch"`
   257  					Version      struct {
   258  						Epoch   string `xml:"epoch,attr"`
   259  						Version string `xml:"ver,attr"`
   260  						Release string `xml:"rel,attr"`
   261  					} `xml:"version"`
   262  					Checksum struct {
   263  						Checksum string `xml:",chardata"`
   264  						Type     string `xml:"type,attr"`
   265  						Pkgid    string `xml:"pkgid,attr"`
   266  					} `xml:"checksum"`
   267  					Summary     string `xml:"summary"`
   268  					Description string `xml:"description"`
   269  					Packager    string `xml:"packager"`
   270  					URL         string `xml:"url"`
   271  					Time        struct {
   272  						File  uint64 `xml:"file,attr"`
   273  						Build uint64 `xml:"build,attr"`
   274  					} `xml:"time"`
   275  					Size struct {
   276  						Package   int64  `xml:"package,attr"`
   277  						Installed uint64 `xml:"installed,attr"`
   278  						Archive   uint64 `xml:"archive,attr"`
   279  					} `xml:"size"`
   280  					Location struct {
   281  						Href string `xml:"href,attr"`
   282  					} `xml:"location"`
   283  					Format struct {
   284  						License   string             `xml:"license"`
   285  						Vendor    string             `xml:"vendor"`
   286  						Group     string             `xml:"group"`
   287  						Buildhost string             `xml:"buildhost"`
   288  						Sourcerpm string             `xml:"sourcerpm"`
   289  						Provides  EntryList          `xml:"provides"`
   290  						Requires  EntryList          `xml:"requires"`
   291  						Conflicts EntryList          `xml:"conflicts"`
   292  						Obsoletes EntryList          `xml:"obsoletes"`
   293  						Files     []*rpm_module.File `xml:"file"`
   294  					} `xml:"format"`
   295  				} `xml:"package"`
   296  			}
   297  
   298  			var result Metadata
   299  			decodeGzipXML(t, resp, &result)
   300  
   301  			assert.EqualValues(t, 1, result.PackageCount)
   302  			assert.Len(t, result.Packages, 1)
   303  			p := result.Packages[0]
   304  			assert.Equal(t, "rpm", p.Type)
   305  			assert.Equal(t, packageName, p.Name)
   306  			assert.Equal(t, packageArchitecture, p.Architecture)
   307  			assert.Equal(t, "YES", p.Checksum.Pkgid)
   308  			assert.Equal(t, "sha256", p.Checksum.Type)
   309  			assert.Equal(t, "f1d5d2ffcbe4a7568e98b864f40d923ecca084e9b9bcd5977ed6521c46d3fa4c", p.Checksum.Checksum)
   310  			assert.Equal(t, "https://gitea.io", p.URL)
   311  			assert.EqualValues(t, len(content), p.Size.Package)
   312  			assert.EqualValues(t, 13, p.Size.Installed)
   313  			assert.EqualValues(t, 272, p.Size.Archive)
   314  			assert.Equal(t, fmt.Sprintf("package/%s/%s/%s", packageName, packageVersion, packageArchitecture), p.Location.Href)
   315  			f := p.Format
   316  			assert.Equal(t, "MIT", f.License)
   317  			assert.Len(t, f.Provides.Entries, 2)
   318  			assert.Len(t, f.Requires.Entries, 7)
   319  			assert.Empty(t, f.Conflicts.Entries)
   320  			assert.Empty(t, f.Obsoletes.Entries)
   321  			assert.Len(t, f.Files, 1)
   322  		})
   323  
   324  		t.Run("filelists.xml.gz", func(t *testing.T) {
   325  			defer tests.PrintCurrentTest(t)()
   326  
   327  			req = NewRequest(t, "GET", url+"/filelists.xml.gz")
   328  			resp := MakeRequest(t, req, http.StatusOK)
   329  
   330  			type Filelists struct {
   331  				XMLName      xml.Name `xml:"filelists"`
   332  				Xmlns        string   `xml:"xmlns,attr"`
   333  				PackageCount int      `xml:"packages,attr"`
   334  				Packages     []struct {
   335  					Pkgid        string `xml:"pkgid,attr"`
   336  					Name         string `xml:"name,attr"`
   337  					Architecture string `xml:"arch,attr"`
   338  					Version      struct {
   339  						Epoch   string `xml:"epoch,attr"`
   340  						Version string `xml:"ver,attr"`
   341  						Release string `xml:"rel,attr"`
   342  					} `xml:"version"`
   343  					Files []*rpm_module.File `xml:"file"`
   344  				} `xml:"package"`
   345  			}
   346  
   347  			var result Filelists
   348  			decodeGzipXML(t, resp, &result)
   349  
   350  			assert.EqualValues(t, 1, result.PackageCount)
   351  			assert.Len(t, result.Packages, 1)
   352  			p := result.Packages[0]
   353  			assert.NotEmpty(t, p.Pkgid)
   354  			assert.Equal(t, packageName, p.Name)
   355  			assert.Equal(t, packageArchitecture, p.Architecture)
   356  			assert.Len(t, p.Files, 1)
   357  			f := p.Files[0]
   358  			assert.Equal(t, "/usr/local/bin/hello", f.Path)
   359  		})
   360  
   361  		t.Run("other.xml.gz", func(t *testing.T) {
   362  			defer tests.PrintCurrentTest(t)()
   363  
   364  			req = NewRequest(t, "GET", url+"/other.xml.gz")
   365  			resp := MakeRequest(t, req, http.StatusOK)
   366  
   367  			type Other struct {
   368  				XMLName      xml.Name `xml:"otherdata"`
   369  				Xmlns        string   `xml:"xmlns,attr"`
   370  				PackageCount int      `xml:"packages,attr"`
   371  				Packages     []struct {
   372  					Pkgid        string `xml:"pkgid,attr"`
   373  					Name         string `xml:"name,attr"`
   374  					Architecture string `xml:"arch,attr"`
   375  					Version      struct {
   376  						Epoch   string `xml:"epoch,attr"`
   377  						Version string `xml:"ver,attr"`
   378  						Release string `xml:"rel,attr"`
   379  					} `xml:"version"`
   380  					Changelogs []*rpm_module.Changelog `xml:"changelog"`
   381  				} `xml:"package"`
   382  			}
   383  
   384  			var result Other
   385  			decodeGzipXML(t, resp, &result)
   386  
   387  			assert.EqualValues(t, 1, result.PackageCount)
   388  			assert.Len(t, result.Packages, 1)
   389  			p := result.Packages[0]
   390  			assert.NotEmpty(t, p.Pkgid)
   391  			assert.Equal(t, packageName, p.Name)
   392  			assert.Equal(t, packageArchitecture, p.Architecture)
   393  			assert.Len(t, p.Changelogs, 1)
   394  			c := p.Changelogs[0]
   395  			assert.Equal(t, "KN4CK3R <dummy@gitea.io>", c.Author)
   396  			assert.EqualValues(t, 1678276800, c.Date)
   397  			assert.Equal(t, "- Changelog message.", c.Text)
   398  		})
   399  	})
   400  
   401  	t.Run("Delete", func(t *testing.T) {
   402  		defer tests.PrintCurrentTest(t)()
   403  
   404  		req := NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
   405  		MakeRequest(t, req, http.StatusUnauthorized)
   406  
   407  		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
   408  		req = AddBasicAuthHeader(req, user.Name)
   409  		MakeRequest(t, req, http.StatusNoContent)
   410  
   411  		pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
   412  		assert.NoError(t, err)
   413  		assert.Empty(t, pvs)
   414  
   415  		req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", rootURL, packageName, packageVersion, packageArchitecture))
   416  		req = AddBasicAuthHeader(req, user.Name)
   417  		MakeRequest(t, req, http.StatusNotFound)
   418  	})
   419  }