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 }