You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

api_packages_rpm_test.go 14KB

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