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_test.go 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "bytes"
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. "testing"
  10. "time"
  11. auth_model "code.gitea.io/gitea/models/auth"
  12. "code.gitea.io/gitea/models/db"
  13. packages_model "code.gitea.io/gitea/models/packages"
  14. container_model "code.gitea.io/gitea/models/packages/container"
  15. "code.gitea.io/gitea/models/unittest"
  16. user_model "code.gitea.io/gitea/models/user"
  17. "code.gitea.io/gitea/modules/setting"
  18. api "code.gitea.io/gitea/modules/structs"
  19. "code.gitea.io/gitea/modules/util"
  20. packages_service "code.gitea.io/gitea/services/packages"
  21. packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
  22. "code.gitea.io/gitea/tests"
  23. "github.com/minio/sha256-simd"
  24. "github.com/stretchr/testify/assert"
  25. )
  26. func TestPackageAPI(t *testing.T) {
  27. defer tests.PrepareTestEnv(t)()
  28. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
  29. session := loginUser(t, user.Name)
  30. tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage)
  31. tokenDeletePackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWritePackage)
  32. packageName := "test-package"
  33. packageVersion := "1.0.3"
  34. filename := "file.bin"
  35. url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", user.Name, packageName, packageVersion, filename)
  36. req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{}))
  37. AddBasicAuthHeader(req, user.Name)
  38. MakeRequest(t, req, http.StatusCreated)
  39. t.Run("ListPackages", func(t *testing.T) {
  40. defer tests.PrintCurrentTest(t)()
  41. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s?token=%s", user.Name, tokenReadPackage))
  42. resp := MakeRequest(t, req, http.StatusOK)
  43. var apiPackages []*api.Package
  44. DecodeJSON(t, resp, &apiPackages)
  45. assert.Len(t, apiPackages, 1)
  46. assert.Equal(t, string(packages_model.TypeGeneric), apiPackages[0].Type)
  47. assert.Equal(t, packageName, apiPackages[0].Name)
  48. assert.Equal(t, packageVersion, apiPackages[0].Version)
  49. assert.NotNil(t, apiPackages[0].Creator)
  50. assert.Equal(t, user.Name, apiPackages[0].Creator.UserName)
  51. })
  52. t.Run("GetPackage", func(t *testing.T) {
  53. defer tests.PrintCurrentTest(t)()
  54. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s?token=%s", user.Name, packageName, packageVersion, tokenReadPackage))
  55. MakeRequest(t, req, http.StatusNotFound)
  56. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, tokenReadPackage))
  57. resp := MakeRequest(t, req, http.StatusOK)
  58. var p *api.Package
  59. DecodeJSON(t, resp, &p)
  60. assert.Equal(t, string(packages_model.TypeGeneric), p.Type)
  61. assert.Equal(t, packageName, p.Name)
  62. assert.Equal(t, packageVersion, p.Version)
  63. assert.NotNil(t, p.Creator)
  64. assert.Equal(t, user.Name, p.Creator.UserName)
  65. t.Run("RepositoryLink", func(t *testing.T) {
  66. defer tests.PrintCurrentTest(t)()
  67. p, err := packages_model.GetPackageByName(db.DefaultContext, user.ID, packages_model.TypeGeneric, packageName)
  68. assert.NoError(t, err)
  69. // no repository link
  70. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, tokenReadPackage))
  71. resp := MakeRequest(t, req, http.StatusOK)
  72. var ap1 *api.Package
  73. DecodeJSON(t, resp, &ap1)
  74. assert.Nil(t, ap1.Repository)
  75. // link to public repository
  76. assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, 1))
  77. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, tokenReadPackage))
  78. resp = MakeRequest(t, req, http.StatusOK)
  79. var ap2 *api.Package
  80. DecodeJSON(t, resp, &ap2)
  81. assert.NotNil(t, ap2.Repository)
  82. assert.EqualValues(t, 1, ap2.Repository.ID)
  83. // link to private repository
  84. assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, 2))
  85. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, tokenReadPackage))
  86. resp = MakeRequest(t, req, http.StatusOK)
  87. var ap3 *api.Package
  88. DecodeJSON(t, resp, &ap3)
  89. assert.Nil(t, ap3.Repository)
  90. assert.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, 2))
  91. })
  92. })
  93. t.Run("ListPackageFiles", func(t *testing.T) {
  94. defer tests.PrintCurrentTest(t)()
  95. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s/files?token=%s", user.Name, packageName, packageVersion, tokenReadPackage))
  96. MakeRequest(t, req, http.StatusNotFound)
  97. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s/files?token=%s", user.Name, packageName, packageVersion, tokenReadPackage))
  98. resp := MakeRequest(t, req, http.StatusOK)
  99. var files []*api.PackageFile
  100. DecodeJSON(t, resp, &files)
  101. assert.Len(t, files, 1)
  102. assert.Equal(t, int64(0), files[0].Size)
  103. assert.Equal(t, filename, files[0].Name)
  104. assert.Equal(t, "d41d8cd98f00b204e9800998ecf8427e", files[0].HashMD5)
  105. assert.Equal(t, "da39a3ee5e6b4b0d3255bfef95601890afd80709", files[0].HashSHA1)
  106. assert.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", files[0].HashSHA256)
  107. assert.Equal(t, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", files[0].HashSHA512)
  108. })
  109. t.Run("DeletePackage", func(t *testing.T) {
  110. defer tests.PrintCurrentTest(t)()
  111. req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/dummy/%s/%s?token=%s", user.Name, packageName, packageVersion, tokenDeletePackage))
  112. MakeRequest(t, req, http.StatusNotFound)
  113. req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s?token=%s", user.Name, packageName, packageVersion, tokenDeletePackage))
  114. MakeRequest(t, req, http.StatusNoContent)
  115. })
  116. }
  117. func TestPackageAccess(t *testing.T) {
  118. defer tests.PrepareTestEnv(t)()
  119. admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
  120. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
  121. inactive := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 9})
  122. limitedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 33})
  123. privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31})
  124. privateOrgMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) // user has package write access
  125. limitedOrgMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 36}) // user has package write access
  126. publicOrgMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 25}) // user has package read access
  127. privateOrgNoMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 35})
  128. limitedOrgNoMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 22})
  129. publicOrgNoMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17})
  130. uploadPackage := func(doer, owner *user_model.User, filename string, expectedStatus int) {
  131. url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/%s.bin", owner.Name, filename)
  132. req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))
  133. if doer != nil {
  134. AddBasicAuthHeader(req, doer.Name)
  135. }
  136. MakeRequest(t, req, expectedStatus)
  137. }
  138. downloadPackage := func(doer, owner *user_model.User, expectedStatus int) {
  139. url := fmt.Sprintf("/api/packages/%s/generic/test-package/1.0/admin.bin", owner.Name)
  140. req := NewRequest(t, "GET", url)
  141. if doer != nil {
  142. AddBasicAuthHeader(req, doer.Name)
  143. }
  144. MakeRequest(t, req, expectedStatus)
  145. }
  146. type Target struct {
  147. Owner *user_model.User
  148. ExpectedStatus int
  149. }
  150. t.Run("Upload", func(t *testing.T) {
  151. defer tests.PrintCurrentTest(t)()
  152. cases := []struct {
  153. Doer *user_model.User
  154. Filename string
  155. Targets []Target
  156. }{
  157. { // Admins can upload to every owner
  158. Doer: admin,
  159. Filename: "admin",
  160. Targets: []Target{
  161. {admin, http.StatusCreated},
  162. {inactive, http.StatusCreated},
  163. {user, http.StatusCreated},
  164. {limitedUser, http.StatusCreated},
  165. {privateUser, http.StatusCreated},
  166. {privateOrgMember, http.StatusCreated},
  167. {limitedOrgMember, http.StatusCreated},
  168. {publicOrgMember, http.StatusCreated},
  169. {privateOrgNoMember, http.StatusCreated},
  170. {limitedOrgNoMember, http.StatusCreated},
  171. {publicOrgNoMember, http.StatusCreated},
  172. },
  173. },
  174. { // Without credentials no upload should be possible
  175. Doer: nil,
  176. Filename: "nil",
  177. Targets: []Target{
  178. {admin, http.StatusUnauthorized},
  179. {inactive, http.StatusUnauthorized},
  180. {user, http.StatusUnauthorized},
  181. {limitedUser, http.StatusUnauthorized},
  182. {privateUser, http.StatusUnauthorized},
  183. {privateOrgMember, http.StatusUnauthorized},
  184. {limitedOrgMember, http.StatusUnauthorized},
  185. {publicOrgMember, http.StatusUnauthorized},
  186. {privateOrgNoMember, http.StatusUnauthorized},
  187. {limitedOrgNoMember, http.StatusUnauthorized},
  188. {publicOrgNoMember, http.StatusUnauthorized},
  189. },
  190. },
  191. { // Inactive users can't upload anywhere
  192. Doer: inactive,
  193. Filename: "inactive",
  194. Targets: []Target{
  195. {admin, http.StatusUnauthorized},
  196. {inactive, http.StatusUnauthorized},
  197. {user, http.StatusUnauthorized},
  198. {limitedUser, http.StatusUnauthorized},
  199. {privateUser, http.StatusUnauthorized},
  200. {privateOrgMember, http.StatusUnauthorized},
  201. {limitedOrgMember, http.StatusUnauthorized},
  202. {publicOrgMember, http.StatusUnauthorized},
  203. {privateOrgNoMember, http.StatusUnauthorized},
  204. {limitedOrgNoMember, http.StatusUnauthorized},
  205. {publicOrgNoMember, http.StatusUnauthorized},
  206. },
  207. },
  208. { // Normal users can upload to self and orgs in which they are members and have package write access
  209. Doer: user,
  210. Filename: "user",
  211. Targets: []Target{
  212. {admin, http.StatusUnauthorized},
  213. {inactive, http.StatusUnauthorized},
  214. {user, http.StatusCreated},
  215. {limitedUser, http.StatusUnauthorized},
  216. {privateUser, http.StatusUnauthorized},
  217. {privateOrgMember, http.StatusCreated},
  218. {limitedOrgMember, http.StatusCreated},
  219. {publicOrgMember, http.StatusUnauthorized},
  220. {privateOrgNoMember, http.StatusUnauthorized},
  221. {limitedOrgNoMember, http.StatusUnauthorized},
  222. {publicOrgNoMember, http.StatusUnauthorized},
  223. },
  224. },
  225. }
  226. for _, c := range cases {
  227. for _, t := range c.Targets {
  228. uploadPackage(c.Doer, t.Owner, c.Filename, t.ExpectedStatus)
  229. }
  230. }
  231. })
  232. t.Run("Download", func(t *testing.T) {
  233. defer tests.PrintCurrentTest(t)()
  234. cases := []struct {
  235. Doer *user_model.User
  236. Filename string
  237. Targets []Target
  238. }{
  239. { // Admins can access everything
  240. Doer: admin,
  241. Targets: []Target{
  242. {admin, http.StatusOK},
  243. {inactive, http.StatusOK},
  244. {user, http.StatusOK},
  245. {limitedUser, http.StatusOK},
  246. {privateUser, http.StatusOK},
  247. {privateOrgMember, http.StatusOK},
  248. {limitedOrgMember, http.StatusOK},
  249. {publicOrgMember, http.StatusOK},
  250. {privateOrgNoMember, http.StatusOK},
  251. {limitedOrgNoMember, http.StatusOK},
  252. {publicOrgNoMember, http.StatusOK},
  253. },
  254. },
  255. { // Without credentials only public owners are accessible
  256. Doer: nil,
  257. Targets: []Target{
  258. {admin, http.StatusOK},
  259. {inactive, http.StatusOK},
  260. {user, http.StatusOK},
  261. {limitedUser, http.StatusUnauthorized},
  262. {privateUser, http.StatusUnauthorized},
  263. {privateOrgMember, http.StatusUnauthorized},
  264. {limitedOrgMember, http.StatusUnauthorized},
  265. {publicOrgMember, http.StatusOK},
  266. {privateOrgNoMember, http.StatusUnauthorized},
  267. {limitedOrgNoMember, http.StatusUnauthorized},
  268. {publicOrgNoMember, http.StatusOK},
  269. },
  270. },
  271. { // Inactive users have no access
  272. Doer: inactive,
  273. Targets: []Target{
  274. {admin, http.StatusUnauthorized},
  275. {inactive, http.StatusUnauthorized},
  276. {user, http.StatusUnauthorized},
  277. {limitedUser, http.StatusUnauthorized},
  278. {privateUser, http.StatusUnauthorized},
  279. {privateOrgMember, http.StatusUnauthorized},
  280. {limitedOrgMember, http.StatusUnauthorized},
  281. {publicOrgMember, http.StatusUnauthorized},
  282. {privateOrgNoMember, http.StatusUnauthorized},
  283. {limitedOrgNoMember, http.StatusUnauthorized},
  284. {publicOrgNoMember, http.StatusUnauthorized},
  285. },
  286. },
  287. { // Normal users can access self, public or limited users/orgs and private orgs in which they are members
  288. Doer: user,
  289. Targets: []Target{
  290. {admin, http.StatusOK},
  291. {inactive, http.StatusOK},
  292. {user, http.StatusOK},
  293. {limitedUser, http.StatusOK},
  294. {privateUser, http.StatusUnauthorized},
  295. {privateOrgMember, http.StatusOK},
  296. {limitedOrgMember, http.StatusOK},
  297. {publicOrgMember, http.StatusOK},
  298. {privateOrgNoMember, http.StatusUnauthorized},
  299. {limitedOrgNoMember, http.StatusOK},
  300. {publicOrgNoMember, http.StatusOK},
  301. },
  302. },
  303. }
  304. for _, c := range cases {
  305. for _, target := range c.Targets {
  306. downloadPackage(c.Doer, target.Owner, target.ExpectedStatus)
  307. }
  308. }
  309. })
  310. t.Run("API", func(t *testing.T) {
  311. defer tests.PrintCurrentTest(t)()
  312. session := loginUser(t, user.Name)
  313. tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage)
  314. for _, target := range []Target{
  315. {admin, http.StatusOK},
  316. {inactive, http.StatusOK},
  317. {user, http.StatusOK},
  318. {limitedUser, http.StatusOK},
  319. {privateUser, http.StatusForbidden},
  320. {privateOrgMember, http.StatusOK},
  321. {limitedOrgMember, http.StatusOK},
  322. {publicOrgMember, http.StatusOK},
  323. {privateOrgNoMember, http.StatusForbidden},
  324. {limitedOrgNoMember, http.StatusOK},
  325. {publicOrgNoMember, http.StatusOK},
  326. } {
  327. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s?token=%s", target.Owner.Name, tokenReadPackage))
  328. MakeRequest(t, req, target.ExpectedStatus)
  329. }
  330. })
  331. }
  332. func TestPackageQuota(t *testing.T) {
  333. defer tests.PrepareTestEnv(t)()
  334. limitTotalOwnerCount, limitTotalOwnerSize := setting.Packages.LimitTotalOwnerCount, setting.Packages.LimitTotalOwnerSize
  335. // Exceeded quota result in StatusForbidden for normal users but admins are always allowed to upload.
  336. admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
  337. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})
  338. t.Run("Common", func(t *testing.T) {
  339. defer tests.PrintCurrentTest(t)()
  340. limitSizeGeneric := setting.Packages.LimitSizeGeneric
  341. uploadPackage := func(doer *user_model.User, version string, expectedStatus int) {
  342. url := fmt.Sprintf("/api/packages/%s/generic/test-package/%s/file.bin", user.Name, version)
  343. req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))
  344. AddBasicAuthHeader(req, doer.Name)
  345. MakeRequest(t, req, expectedStatus)
  346. }
  347. setting.Packages.LimitTotalOwnerCount = 0
  348. uploadPackage(user, "1.0", http.StatusForbidden)
  349. uploadPackage(admin, "1.0", http.StatusCreated)
  350. setting.Packages.LimitTotalOwnerCount = limitTotalOwnerCount
  351. setting.Packages.LimitTotalOwnerSize = 0
  352. uploadPackage(user, "1.1", http.StatusForbidden)
  353. uploadPackage(admin, "1.1", http.StatusCreated)
  354. setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize
  355. setting.Packages.LimitSizeGeneric = 0
  356. uploadPackage(user, "1.2", http.StatusForbidden)
  357. uploadPackage(admin, "1.2", http.StatusCreated)
  358. setting.Packages.LimitSizeGeneric = limitSizeGeneric
  359. })
  360. t.Run("Container", func(t *testing.T) {
  361. defer tests.PrintCurrentTest(t)()
  362. limitSizeContainer := setting.Packages.LimitSizeContainer
  363. uploadBlob := func(doer *user_model.User, data string, expectedStatus int) {
  364. url := fmt.Sprintf("/v2/%s/quota-test/blobs/uploads?digest=sha256:%x", user.Name, sha256.Sum256([]byte(data)))
  365. req := NewRequestWithBody(t, "POST", url, strings.NewReader(data))
  366. AddBasicAuthHeader(req, doer.Name)
  367. MakeRequest(t, req, expectedStatus)
  368. }
  369. setting.Packages.LimitTotalOwnerSize = 0
  370. uploadBlob(user, "2", http.StatusForbidden)
  371. uploadBlob(admin, "2", http.StatusCreated)
  372. setting.Packages.LimitTotalOwnerSize = limitTotalOwnerSize
  373. setting.Packages.LimitSizeContainer = 0
  374. uploadBlob(user, "3", http.StatusForbidden)
  375. uploadBlob(admin, "3", http.StatusCreated)
  376. setting.Packages.LimitSizeContainer = limitSizeContainer
  377. })
  378. }
  379. func TestPackageCleanup(t *testing.T) {
  380. defer tests.PrepareTestEnv(t)()
  381. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  382. duration, _ := time.ParseDuration("-1h")
  383. t.Run("Common", func(t *testing.T) {
  384. defer tests.PrintCurrentTest(t)()
  385. // Upload and delete a generic package and upload a container blob
  386. data, _ := util.CryptoRandomBytes(5)
  387. url := fmt.Sprintf("/api/packages/%s/generic/cleanup-test/1.1.1/file.bin", user.Name)
  388. req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(data))
  389. AddBasicAuthHeader(req, user.Name)
  390. MakeRequest(t, req, http.StatusCreated)
  391. req = NewRequest(t, "DELETE", url)
  392. AddBasicAuthHeader(req, user.Name)
  393. MakeRequest(t, req, http.StatusNoContent)
  394. data, _ = util.CryptoRandomBytes(5)
  395. url = fmt.Sprintf("/v2/%s/cleanup-test/blobs/uploads?digest=sha256:%x", user.Name, sha256.Sum256(data))
  396. req = NewRequestWithBody(t, "POST", url, bytes.NewReader(data))
  397. AddBasicAuthHeader(req, user.Name)
  398. MakeRequest(t, req, http.StatusCreated)
  399. pbs, err := packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, duration)
  400. assert.NoError(t, err)
  401. assert.NotEmpty(t, pbs)
  402. _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion)
  403. assert.NoError(t, err)
  404. err = packages_cleanup_service.CleanupTask(db.DefaultContext, duration)
  405. assert.NoError(t, err)
  406. pbs, err = packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, duration)
  407. assert.NoError(t, err)
  408. assert.Empty(t, pbs)
  409. _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion)
  410. assert.ErrorIs(t, err, packages_model.ErrPackageNotExist)
  411. })
  412. t.Run("CleanupRules", func(t *testing.T) {
  413. defer tests.PrintCurrentTest(t)()
  414. type version struct {
  415. Version string
  416. ShouldExist bool
  417. Created int64
  418. }
  419. cases := []struct {
  420. Name string
  421. Versions []version
  422. Rule *packages_model.PackageCleanupRule
  423. }{
  424. {
  425. Name: "Disabled",
  426. Versions: []version{
  427. {Version: "keep", ShouldExist: true},
  428. },
  429. Rule: &packages_model.PackageCleanupRule{
  430. Enabled: false,
  431. },
  432. },
  433. {
  434. Name: "KeepCount",
  435. Versions: []version{
  436. {Version: "keep", ShouldExist: true},
  437. {Version: "v1.0", ShouldExist: true},
  438. {Version: "test-3", ShouldExist: false, Created: 1},
  439. {Version: "test-4", ShouldExist: false, Created: 1},
  440. },
  441. Rule: &packages_model.PackageCleanupRule{
  442. Enabled: true,
  443. KeepCount: 2,
  444. },
  445. },
  446. {
  447. Name: "KeepPattern",
  448. Versions: []version{
  449. {Version: "keep", ShouldExist: true},
  450. {Version: "v1.0", ShouldExist: false},
  451. },
  452. Rule: &packages_model.PackageCleanupRule{
  453. Enabled: true,
  454. KeepPattern: "k.+p",
  455. },
  456. },
  457. {
  458. Name: "RemoveDays",
  459. Versions: []version{
  460. {Version: "keep", ShouldExist: true},
  461. {Version: "v1.0", ShouldExist: false, Created: 1},
  462. },
  463. Rule: &packages_model.PackageCleanupRule{
  464. Enabled: true,
  465. RemoveDays: 60,
  466. },
  467. },
  468. {
  469. Name: "RemovePattern",
  470. Versions: []version{
  471. {Version: "test", ShouldExist: true},
  472. {Version: "test-3", ShouldExist: false},
  473. {Version: "test-4", ShouldExist: false},
  474. },
  475. Rule: &packages_model.PackageCleanupRule{
  476. Enabled: true,
  477. RemovePattern: `t[e]+st-\d+`,
  478. },
  479. },
  480. {
  481. Name: "MatchFullName",
  482. Versions: []version{
  483. {Version: "keep", ShouldExist: true},
  484. {Version: "test", ShouldExist: false},
  485. },
  486. Rule: &packages_model.PackageCleanupRule{
  487. Enabled: true,
  488. RemovePattern: `package/test|different/keep`,
  489. MatchFullName: true,
  490. },
  491. },
  492. {
  493. Name: "Mixed",
  494. Versions: []version{
  495. {Version: "keep", ShouldExist: true, Created: time.Now().Add(time.Duration(10000)).Unix()},
  496. {Version: "dummy", ShouldExist: true, Created: 1},
  497. {Version: "test-3", ShouldExist: true},
  498. {Version: "test-4", ShouldExist: false, Created: 1},
  499. },
  500. Rule: &packages_model.PackageCleanupRule{
  501. Enabled: true,
  502. KeepCount: 1,
  503. KeepPattern: `dummy`,
  504. RemoveDays: 7,
  505. RemovePattern: `t[e]+st-\d+`,
  506. },
  507. },
  508. }
  509. for _, c := range cases {
  510. t.Run(c.Name, func(t *testing.T) {
  511. defer tests.PrintCurrentTest(t)()
  512. for _, v := range c.Versions {
  513. url := fmt.Sprintf("/api/packages/%s/generic/package/%s/file.bin", user.Name, v.Version)
  514. req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1}))
  515. AddBasicAuthHeader(req, user.Name)
  516. MakeRequest(t, req, http.StatusCreated)
  517. if v.Created != 0 {
  518. pv, err := packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeGeneric, "package", v.Version)
  519. assert.NoError(t, err)
  520. _, err = db.GetEngine(db.DefaultContext).Exec("UPDATE package_version SET created_unix = ? WHERE id = ?", v.Created, pv.ID)
  521. assert.NoError(t, err)
  522. }
  523. }
  524. c.Rule.OwnerID = user.ID
  525. c.Rule.Type = packages_model.TypeGeneric
  526. pcr, err := packages_model.InsertCleanupRule(db.DefaultContext, c.Rule)
  527. assert.NoError(t, err)
  528. err = packages_cleanup_service.CleanupTask(db.DefaultContext, duration)
  529. assert.NoError(t, err)
  530. for _, v := range c.Versions {
  531. pv, err := packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeGeneric, "package", v.Version)
  532. if v.ShouldExist {
  533. assert.NoError(t, err)
  534. err = packages_service.DeletePackageVersionAndReferences(db.DefaultContext, pv)
  535. assert.NoError(t, err)
  536. } else {
  537. assert.ErrorIs(t, err, packages_model.ErrPackageNotExist)
  538. }
  539. }
  540. assert.NoError(t, packages_model.DeleteCleanupRuleByID(db.DefaultContext, pcr.ID))
  541. })
  542. }
  543. })
  544. }