code.gitea.io/gitea@v1.22.3/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  	"strings"
    16  	"testing"
    17  
    18  	"code.gitea.io/gitea/models/db"
    19  	"code.gitea.io/gitea/models/packages"
    20  	"code.gitea.io/gitea/models/unittest"
    21  	user_model "code.gitea.io/gitea/models/user"
    22  	rpm_module "code.gitea.io/gitea/modules/packages/rpm"
    23  	"code.gitea.io/gitea/modules/setting"
    24  	"code.gitea.io/gitea/modules/util"
    25  	"code.gitea.io/gitea/tests"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  )
    29  
    30  func TestPackageRpm(t *testing.T) {
    31  	defer tests.PrepareTestEnv(t)()
    32  
    33  	packageName := "gitea-test"
    34  	packageVersion := "1.0.2-1"
    35  	packageArchitecture := "x86_64"
    36  
    37  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
    38  
    39  	base64RpmPackageContent := `H4sICFayB2QCAGdpdGVhLXRlc3QtMS4wLjItMS14ODZfNjQucnBtAO2YV4gTQRjHJzl7wbNhhxVF
    40  VNwk2zd2PdvZ9Sxnd3Z3NllNsmF3o6congVFsWFHRWwIImIXfRER0QcRfPBJEXvvBQvWSfZTT0VQ
    41  8TF/MuU33zcz3+zOJGEe73lyuQBRBWKWRzDrEddjuVAkxLMc+lsFUOWfm5bvvReAalWECg/TsivU
    42  dyKa0U61aVnl6wj0Uxe4nc8F92hZiaYE8CO/P0r7/Quegr0c7M/AvoCaGZEIWNGUqMHrhhGROIUT
    43  Zc7gOAOraoQzCNZ0WdU0HpEI5jiB4zlek3gT85wqCBomhomxoGCs8wImWMImbxqKgXVNUKKaqShR
    44  STKVKK9glFUNcf2g+/t27xs16v5x/eyOKftVGlIhyiuvvPLKK6+88sorr7zyyiuvvPKCO5HPnz+v
    45  pGVhhXsTsFVeSstuWR9anwU+Bk3Vch5wTwL3JkHg+8C1gR8A169wj1KdpobAj4HbAT+Be5VewE+h
    46  fz/g52AvBX4N9vHAb4AnA7+F8ePAH8BuA38ELgf+BLzQ50oIeBlw0OdAOXAlP57AGuCsbwGtbgCu
    47  DrwRuAb4bwau6T/PwFbgWsDXgWuD/y3gOmC/B1wI/Bi4AcT3Arih3z9YCNzI9w9m/YKUG4Nd9N9z
    48  pSZgHwrcFPgccFt//OADGE+F/q+Ao+D/FrijzwV1gbv4/QvaAHcFDgF3B5aB+wB3Be7rz1dQCtwP
    49  eDxwMcw3GbgU7AasdwzYE8DjwT4L/CeAvRx4IvBCYA3iWQds+FzpDjABfghsAj8BTgA/A/b8+StX
    50  A84A1wKe5s9fuRB4JpzHZv55rL8a/Dv49vpn/PErR4BvQX8Z+Db4l2W5CH2/f0W5+1fEoeFDBzFp
    51  rE/FMcK4mWQSOzN+aDOIqztW2rPsFKIyqh7sQERR42RVMSKihnzVHlQ8Ag0YLBYNEIajkhmuR5Io
    52  7nlpt2M4nJs0ZNkoYaUyZahMlSfJImr1n1WjFVNCPCaTZgYNGdGL8YN2mX8WHfA/C7ViHJK0pxHG
    53  SrkeTiSI4T+7ubf85yrzRCQRQ5EVxVAjvIBVRY/KRFAVReIkhfARSddNSceayQkGliIKb0q8RAxJ
    54  5QWNVxHIsW3Pz369bw+5jh5y0klE9Znqm0dF57b0HbGy2A5lVUBTZZrqZjdUjYoprFmpsBtHP5d0
    55  +ISltS2yk2mHuC4x+lgJMhgnidvuqy3b0suK0bm+tw3FMxI2zjm7/fA0MtQhplX2s7nYLZ2ZC0yg
    56  CxJZDokhORTJlrlcCvG5OieGBERlVCs7CfuS6WzQ/T2j+9f92BWxTFEcp2IkYccYGp2LYySEfreq
    57  irue4WRF5XkpKovw2wgpq2rZBI8bQZkzxEkiYaNwxnXCCVvHidzIiB3CM2yMYdNWmjDsaLovaE4c
    58  x3a6mLaTxB7rEj3jWN4M2p7uwPaa1GfI8BHFfcZMKhkycnhR7y781/a+A4t7FpWWTupRUtKbegwZ
    59  XMKwJinTSe70uhRcj55qNu3YHtE922Fdz7FTMTq9Q3TbMdiYrrPudMvT44S6u2miu138eC0tTN9D
    60  2CFGHHtQsHHsGCRFDFbXuT9wx6mUTZfseydlkWZeJkW6xOgYjqXT+LA7I6XHaUx2xmUzqelWymA9
    61  rCXI9+D1BHbjsITssqhBNysw0tOWjcpmIh6+aViYPfftw8ZSGfRVPUqKiosZj5R5qGmk/8AjjRbZ
    62  d8b3vvngdPHx3HvMeCarIk7VVSwbgoZVkceEVyOmyUmGxBGNYDVKSFSOGlIkGqWnUZFkiY/wsmhK
    63  Mu0UFYgZ/bYnuvn/vz4wtCz8qMwsHUvP0PX3tbYFUctAPdrY6tiiDtcCddDECahx7SuVNP5dpmb5
    64  9tMDyaXb7OAlk5acuPn57ss9mw6Wym0m1Fq2cej7tUt2LL4/b8enXU2fndk+fvv57ndnt55/cQob
    65  7tpp/pEjDS7cGPZ6BY430+7danDq6f42Nw49b9F7zp6BiKpJb9s5P0AYN2+L159cnrur636rx+v1
    66  7ae1K28QbMMcqI8CqwIrgwg9nTOp8Oj9q81plUY7ZuwXN8Vvs8wbAAA=`
    67  	rpmPackageContent, err := base64.StdEncoding.DecodeString(base64RpmPackageContent)
    68  	assert.NoError(t, err)
    69  
    70  	zr, err := gzip.NewReader(bytes.NewReader(rpmPackageContent))
    71  	assert.NoError(t, err)
    72  
    73  	content, err := io.ReadAll(zr)
    74  	assert.NoError(t, err)
    75  
    76  	rootURL := fmt.Sprintf("/api/packages/%s/rpm", user.Name)
    77  
    78  	for _, group := range []string{"", "el9", "el9/stable"} {
    79  		t.Run(fmt.Sprintf("[Group:%s]", group), func(t *testing.T) {
    80  			var groupParts []string
    81  			if group != "" {
    82  				groupParts = strings.Split(group, "/")
    83  			}
    84  			groupURL := strings.Join(append([]string{rootURL}, groupParts...), "/")
    85  
    86  			t.Run("RepositoryConfig", func(t *testing.T) {
    87  				defer tests.PrintCurrentTest(t)()
    88  
    89  				req := NewRequest(t, "GET", groupURL+".repo")
    90  				resp := MakeRequest(t, req, http.StatusOK)
    91  
    92  				expected := fmt.Sprintf(`[gitea-%s]
    93  name=%s
    94  baseurl=%s
    95  enabled=1
    96  gpgcheck=1
    97  gpgkey=%sapi/packages/%s/rpm/repository.key`,
    98  					strings.Join(append([]string{user.LowerName}, groupParts...), "-"),
    99  					strings.Join(append([]string{user.Name, setting.AppName}, groupParts...), " - "),
   100  					util.URLJoin(setting.AppURL, groupURL),
   101  					setting.AppURL,
   102  					user.Name,
   103  				)
   104  
   105  				assert.Equal(t, expected, resp.Body.String())
   106  			})
   107  
   108  			t.Run("RepositoryKey", func(t *testing.T) {
   109  				defer tests.PrintCurrentTest(t)()
   110  
   111  				req := NewRequest(t, "GET", rootURL+"/repository.key")
   112  				resp := MakeRequest(t, req, http.StatusOK)
   113  
   114  				assert.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type"))
   115  				assert.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----")
   116  			})
   117  
   118  			t.Run("Upload", func(t *testing.T) {
   119  				url := groupURL + "/upload"
   120  
   121  				req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content))
   122  				MakeRequest(t, req, http.StatusUnauthorized)
   123  
   124  				req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
   125  					AddBasicAuth(user.Name)
   126  				MakeRequest(t, req, http.StatusCreated)
   127  
   128  				pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
   129  				assert.NoError(t, err)
   130  				assert.Len(t, pvs, 1)
   131  
   132  				pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
   133  				assert.NoError(t, err)
   134  				assert.Nil(t, pd.SemVer)
   135  				assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata)
   136  				assert.Equal(t, packageName, pd.Package.Name)
   137  				assert.Equal(t, packageVersion, pd.Version.Version)
   138  
   139  				pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
   140  				assert.NoError(t, err)
   141  				assert.Len(t, pfs, 1)
   142  				assert.Equal(t, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture), pfs[0].Name)
   143  				assert.True(t, pfs[0].IsLead)
   144  
   145  				pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
   146  				assert.NoError(t, err)
   147  				assert.Equal(t, int64(len(content)), pb.Size)
   148  
   149  				req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
   150  					AddBasicAuth(user.Name)
   151  				MakeRequest(t, req, http.StatusConflict)
   152  			})
   153  
   154  			t.Run("Download", func(t *testing.T) {
   155  				defer tests.PrintCurrentTest(t)()
   156  
   157  				req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
   158  				resp := MakeRequest(t, req, http.StatusOK)
   159  
   160  				assert.Equal(t, content, resp.Body.Bytes())
   161  			})
   162  
   163  			t.Run("Repository", func(t *testing.T) {
   164  				defer tests.PrintCurrentTest(t)()
   165  
   166  				url := groupURL + "/repodata"
   167  
   168  				req := NewRequest(t, "HEAD", url+"/dummy.xml")
   169  				MakeRequest(t, req, http.StatusNotFound)
   170  
   171  				req = NewRequest(t, "GET", url+"/dummy.xml")
   172  				MakeRequest(t, req, http.StatusNotFound)
   173  
   174  				t.Run("repomd.xml", func(t *testing.T) {
   175  					defer tests.PrintCurrentTest(t)()
   176  
   177  					req = NewRequest(t, "HEAD", url+"/repomd.xml")
   178  					MakeRequest(t, req, http.StatusOK)
   179  
   180  					req = NewRequest(t, "GET", url+"/repomd.xml")
   181  					resp := MakeRequest(t, req, http.StatusOK)
   182  
   183  					type Repomd struct {
   184  						XMLName  xml.Name `xml:"repomd"`
   185  						Xmlns    string   `xml:"xmlns,attr"`
   186  						XmlnsRpm string   `xml:"xmlns:rpm,attr"`
   187  						Data     []struct {
   188  							Type     string `xml:"type,attr"`
   189  							Checksum struct {
   190  								Value string `xml:",chardata"`
   191  								Type  string `xml:"type,attr"`
   192  							} `xml:"checksum"`
   193  							OpenChecksum struct {
   194  								Value string `xml:",chardata"`
   195  								Type  string `xml:"type,attr"`
   196  							} `xml:"open-checksum"`
   197  							Location struct {
   198  								Href string `xml:"href,attr"`
   199  							} `xml:"location"`
   200  							Timestamp int64 `xml:"timestamp"`
   201  							Size      int64 `xml:"size"`
   202  							OpenSize  int64 `xml:"open-size"`
   203  						} `xml:"data"`
   204  					}
   205  
   206  					var result Repomd
   207  					decodeXML(t, resp, &result)
   208  
   209  					assert.Len(t, result.Data, 3)
   210  					for _, d := range result.Data {
   211  						assert.Equal(t, "sha256", d.Checksum.Type)
   212  						assert.NotEmpty(t, d.Checksum.Value)
   213  						assert.Equal(t, "sha256", d.OpenChecksum.Type)
   214  						assert.NotEmpty(t, d.OpenChecksum.Value)
   215  						assert.NotEqual(t, d.Checksum.Value, d.OpenChecksum.Value)
   216  						assert.Greater(t, d.OpenSize, d.Size)
   217  
   218  						switch d.Type {
   219  						case "primary":
   220  							assert.EqualValues(t, 722, d.Size)
   221  							assert.EqualValues(t, 1759, d.OpenSize)
   222  							assert.Equal(t, "repodata/primary.xml.gz", d.Location.Href)
   223  						case "filelists":
   224  							assert.EqualValues(t, 257, d.Size)
   225  							assert.EqualValues(t, 326, d.OpenSize)
   226  							assert.Equal(t, "repodata/filelists.xml.gz", d.Location.Href)
   227  						case "other":
   228  							assert.EqualValues(t, 306, d.Size)
   229  							assert.EqualValues(t, 394, d.OpenSize)
   230  							assert.Equal(t, "repodata/other.xml.gz", d.Location.Href)
   231  						}
   232  					}
   233  				})
   234  
   235  				t.Run("repomd.xml.asc", func(t *testing.T) {
   236  					defer tests.PrintCurrentTest(t)()
   237  
   238  					req = NewRequest(t, "GET", url+"/repomd.xml.asc")
   239  					resp := MakeRequest(t, req, http.StatusOK)
   240  
   241  					assert.Contains(t, resp.Body.String(), "-----BEGIN PGP SIGNATURE-----")
   242  				})
   243  
   244  				decodeGzipXML := func(t testing.TB, resp *httptest.ResponseRecorder, v any) {
   245  					t.Helper()
   246  
   247  					zr, err := gzip.NewReader(resp.Body)
   248  					assert.NoError(t, err)
   249  
   250  					assert.NoError(t, xml.NewDecoder(zr).Decode(v))
   251  				}
   252  
   253  				t.Run("primary.xml.gz", func(t *testing.T) {
   254  					defer tests.PrintCurrentTest(t)()
   255  
   256  					req = NewRequest(t, "GET", url+"/primary.xml.gz")
   257  					resp := MakeRequest(t, req, http.StatusOK)
   258  
   259  					type EntryList struct {
   260  						Entries []*rpm_module.Entry `xml:"entry"`
   261  					}
   262  
   263  					type Metadata struct {
   264  						XMLName      xml.Name `xml:"metadata"`
   265  						Xmlns        string   `xml:"xmlns,attr"`
   266  						XmlnsRpm     string   `xml:"xmlns:rpm,attr"`
   267  						PackageCount int      `xml:"packages,attr"`
   268  						Packages     []struct {
   269  							XMLName      xml.Name `xml:"package"`
   270  							Type         string   `xml:"type,attr"`
   271  							Name         string   `xml:"name"`
   272  							Architecture string   `xml:"arch"`
   273  							Version      struct {
   274  								Epoch   string `xml:"epoch,attr"`
   275  								Version string `xml:"ver,attr"`
   276  								Release string `xml:"rel,attr"`
   277  							} `xml:"version"`
   278  							Checksum struct {
   279  								Checksum string `xml:",chardata"`
   280  								Type     string `xml:"type,attr"`
   281  								Pkgid    string `xml:"pkgid,attr"`
   282  							} `xml:"checksum"`
   283  							Summary     string `xml:"summary"`
   284  							Description string `xml:"description"`
   285  							Packager    string `xml:"packager"`
   286  							URL         string `xml:"url"`
   287  							Time        struct {
   288  								File  uint64 `xml:"file,attr"`
   289  								Build uint64 `xml:"build,attr"`
   290  							} `xml:"time"`
   291  							Size struct {
   292  								Package   int64  `xml:"package,attr"`
   293  								Installed uint64 `xml:"installed,attr"`
   294  								Archive   uint64 `xml:"archive,attr"`
   295  							} `xml:"size"`
   296  							Location struct {
   297  								Href string `xml:"href,attr"`
   298  							} `xml:"location"`
   299  							Format struct {
   300  								License   string             `xml:"license"`
   301  								Vendor    string             `xml:"vendor"`
   302  								Group     string             `xml:"group"`
   303  								Buildhost string             `xml:"buildhost"`
   304  								Sourcerpm string             `xml:"sourcerpm"`
   305  								Provides  EntryList          `xml:"provides"`
   306  								Requires  EntryList          `xml:"requires"`
   307  								Conflicts EntryList          `xml:"conflicts"`
   308  								Obsoletes EntryList          `xml:"obsoletes"`
   309  								Files     []*rpm_module.File `xml:"file"`
   310  							} `xml:"format"`
   311  						} `xml:"package"`
   312  					}
   313  
   314  					var result Metadata
   315  					decodeGzipXML(t, resp, &result)
   316  
   317  					assert.EqualValues(t, 1, result.PackageCount)
   318  					assert.Len(t, result.Packages, 1)
   319  					p := result.Packages[0]
   320  					assert.Equal(t, "rpm", p.Type)
   321  					assert.Equal(t, packageName, p.Name)
   322  					assert.Equal(t, packageArchitecture, p.Architecture)
   323  					assert.Equal(t, "YES", p.Checksum.Pkgid)
   324  					assert.Equal(t, "sha256", p.Checksum.Type)
   325  					assert.Equal(t, "f1d5d2ffcbe4a7568e98b864f40d923ecca084e9b9bcd5977ed6521c46d3fa4c", p.Checksum.Checksum)
   326  					assert.Equal(t, "https://gitea.io", p.URL)
   327  					assert.EqualValues(t, len(content), p.Size.Package)
   328  					assert.EqualValues(t, 13, p.Size.Installed)
   329  					assert.EqualValues(t, 272, p.Size.Archive)
   330  					assert.Equal(t, fmt.Sprintf("package/%s/%s/%s/%s", packageName, packageVersion, packageArchitecture, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture)), p.Location.Href)
   331  					f := p.Format
   332  					assert.Equal(t, "MIT", f.License)
   333  					assert.Len(t, f.Provides.Entries, 2)
   334  					assert.Len(t, f.Requires.Entries, 7)
   335  					assert.Empty(t, f.Conflicts.Entries)
   336  					assert.Empty(t, f.Obsoletes.Entries)
   337  					assert.Len(t, f.Files, 1)
   338  				})
   339  
   340  				t.Run("filelists.xml.gz", func(t *testing.T) {
   341  					defer tests.PrintCurrentTest(t)()
   342  
   343  					req = NewRequest(t, "GET", url+"/filelists.xml.gz")
   344  					resp := MakeRequest(t, req, http.StatusOK)
   345  
   346  					type Filelists struct {
   347  						XMLName      xml.Name `xml:"filelists"`
   348  						Xmlns        string   `xml:"xmlns,attr"`
   349  						PackageCount int      `xml:"packages,attr"`
   350  						Packages     []struct {
   351  							Pkgid        string `xml:"pkgid,attr"`
   352  							Name         string `xml:"name,attr"`
   353  							Architecture string `xml:"arch,attr"`
   354  							Version      struct {
   355  								Epoch   string `xml:"epoch,attr"`
   356  								Version string `xml:"ver,attr"`
   357  								Release string `xml:"rel,attr"`
   358  							} `xml:"version"`
   359  							Files []*rpm_module.File `xml:"file"`
   360  						} `xml:"package"`
   361  					}
   362  
   363  					var result Filelists
   364  					decodeGzipXML(t, resp, &result)
   365  
   366  					assert.EqualValues(t, 1, result.PackageCount)
   367  					assert.Len(t, result.Packages, 1)
   368  					p := result.Packages[0]
   369  					assert.NotEmpty(t, p.Pkgid)
   370  					assert.Equal(t, packageName, p.Name)
   371  					assert.Equal(t, packageArchitecture, p.Architecture)
   372  					assert.Len(t, p.Files, 1)
   373  					f := p.Files[0]
   374  					assert.Equal(t, "/usr/local/bin/hello", f.Path)
   375  				})
   376  
   377  				t.Run("other.xml.gz", func(t *testing.T) {
   378  					defer tests.PrintCurrentTest(t)()
   379  
   380  					req = NewRequest(t, "GET", url+"/other.xml.gz")
   381  					resp := MakeRequest(t, req, http.StatusOK)
   382  
   383  					type Other struct {
   384  						XMLName      xml.Name `xml:"otherdata"`
   385  						Xmlns        string   `xml:"xmlns,attr"`
   386  						PackageCount int      `xml:"packages,attr"`
   387  						Packages     []struct {
   388  							Pkgid        string `xml:"pkgid,attr"`
   389  							Name         string `xml:"name,attr"`
   390  							Architecture string `xml:"arch,attr"`
   391  							Version      struct {
   392  								Epoch   string `xml:"epoch,attr"`
   393  								Version string `xml:"ver,attr"`
   394  								Release string `xml:"rel,attr"`
   395  							} `xml:"version"`
   396  							Changelogs []*rpm_module.Changelog `xml:"changelog"`
   397  						} `xml:"package"`
   398  					}
   399  
   400  					var result Other
   401  					decodeGzipXML(t, resp, &result)
   402  
   403  					assert.EqualValues(t, 1, result.PackageCount)
   404  					assert.Len(t, result.Packages, 1)
   405  					p := result.Packages[0]
   406  					assert.NotEmpty(t, p.Pkgid)
   407  					assert.Equal(t, packageName, p.Name)
   408  					assert.Equal(t, packageArchitecture, p.Architecture)
   409  					assert.Len(t, p.Changelogs, 1)
   410  					c := p.Changelogs[0]
   411  					assert.Equal(t, "KN4CK3R <dummy@gitea.io>", c.Author)
   412  					assert.EqualValues(t, 1678276800, c.Date)
   413  					assert.Equal(t, "- Changelog message.", c.Text)
   414  				})
   415  			})
   416  
   417  			t.Run("Delete", func(t *testing.T) {
   418  				defer tests.PrintCurrentTest(t)()
   419  
   420  				req := NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
   421  				MakeRequest(t, req, http.StatusUnauthorized)
   422  
   423  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)).
   424  					AddBasicAuth(user.Name)
   425  				MakeRequest(t, req, http.StatusNoContent)
   426  
   427  				pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRpm)
   428  				assert.NoError(t, err)
   429  				assert.Empty(t, pvs)
   430  				req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)).
   431  					AddBasicAuth(user.Name)
   432  				MakeRequest(t, req, http.StatusNotFound)
   433  			})
   434  		})
   435  	}
   436  }