gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "net/http"
  6. "strings"
  7. "testing"
  8. "code.gitea.io/gitea/tests"
  9. "github.com/stretchr/testify/assert"
  10. )
  11. type uploadArtifactResponse struct {
  12. FileContainerResourceURL string `json:"fileContainerResourceUrl"`
  13. }
  14. type getUploadArtifactRequest struct {
  15. Type string
  16. Name string
  17. RetentionDays int64
  18. }
  19. func prepareTestEnvActionsArtifacts(t *testing.T) func() {
  20. t.Helper()
  21. f := tests.PrepareTestEnv(t, 1)
  22. tests.PrepareArtifactsStorage(t)
  23. return f
  24. }
  25. func TestActionsArtifactUploadSingleFile(t *testing.T) {
  26. defer prepareTestEnvActionsArtifacts(t)()
  27. // acquire artifact upload url
  28. req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{
  29. Type: "actions_storage",
  30. Name: "artifact",
  31. }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  32. resp := MakeRequest(t, req, http.StatusOK)
  33. var uploadResp uploadArtifactResponse
  34. DecodeJSON(t, resp, &uploadResp)
  35. assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
  36. // get upload url
  37. idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/")
  38. url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=artifact/abc-2.txt"
  39. // upload artifact chunk
  40. body := strings.Repeat("C", 1024)
  41. req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)).
  42. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a").
  43. SetHeader("Content-Range", "bytes 0-1023/1024").
  44. SetHeader("x-tfs-filelength", "1024").
  45. SetHeader("x-actions-results-md5", "XVlf820rMInUi64wmMi6EA==") // base64(md5(body))
  46. MakeRequest(t, req, http.StatusOK)
  47. t.Logf("Create artifact confirm")
  48. // confirm artifact upload
  49. req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-single").
  50. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  51. MakeRequest(t, req, http.StatusOK)
  52. }
  53. func TestActionsArtifactUploadInvalidHash(t *testing.T) {
  54. defer prepareTestEnvActionsArtifacts(t)()
  55. // artifact id 54321 not exist
  56. url := "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts/8e5b948a454515dbabfc7eb718ddddddd/upload?itemPath=artifact/abc.txt"
  57. body := strings.Repeat("A", 1024)
  58. req := NewRequestWithBody(t, "PUT", url, strings.NewReader(body)).
  59. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a").
  60. SetHeader("Content-Range", "bytes 0-1023/1024").
  61. SetHeader("x-tfs-filelength", "1024").
  62. SetHeader("x-actions-results-md5", "1HsSe8LeLWh93ILaw1TEFQ==") // base64(md5(body))
  63. resp := MakeRequest(t, req, http.StatusBadRequest)
  64. assert.Contains(t, resp.Body.String(), "Invalid artifact hash")
  65. }
  66. func TestActionsArtifactConfirmUploadWithoutName(t *testing.T) {
  67. defer prepareTestEnvActionsArtifacts(t)()
  68. req := NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts").
  69. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  70. resp := MakeRequest(t, req, http.StatusBadRequest)
  71. assert.Contains(t, resp.Body.String(), "artifact name is empty")
  72. }
  73. func TestActionsArtifactUploadWithoutToken(t *testing.T) {
  74. defer prepareTestEnvActionsArtifacts(t)()
  75. req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/1/artifacts", nil)
  76. MakeRequest(t, req, http.StatusUnauthorized)
  77. }
  78. type (
  79. listArtifactsResponseItem struct {
  80. Name string `json:"name"`
  81. FileContainerResourceURL string `json:"fileContainerResourceUrl"`
  82. }
  83. listArtifactsResponse struct {
  84. Count int64 `json:"count"`
  85. Value []listArtifactsResponseItem `json:"value"`
  86. }
  87. downloadArtifactResponseItem struct {
  88. Path string `json:"path"`
  89. ItemType string `json:"itemType"`
  90. ContentLocation string `json:"contentLocation"`
  91. }
  92. downloadArtifactResponse struct {
  93. Value []downloadArtifactResponseItem `json:"value"`
  94. }
  95. )
  96. func TestActionsArtifactDownload(t *testing.T) {
  97. defer prepareTestEnvActionsArtifacts(t)()
  98. req := NewRequest(t, "GET", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts").
  99. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  100. resp := MakeRequest(t, req, http.StatusOK)
  101. var listResp listArtifactsResponse
  102. DecodeJSON(t, resp, &listResp)
  103. assert.Equal(t, int64(2), listResp.Count)
  104. // Return list might be in any order. Get one file.
  105. var artifactIdx int
  106. for i, artifact := range listResp.Value {
  107. if artifact.Name == "artifact-download" {
  108. artifactIdx = i
  109. break
  110. }
  111. }
  112. assert.NotNil(t, artifactIdx)
  113. assert.Equal(t, "artifact-download", listResp.Value[artifactIdx].Name)
  114. assert.Contains(t, listResp.Value[artifactIdx].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
  115. idx := strings.Index(listResp.Value[artifactIdx].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/")
  116. url := listResp.Value[artifactIdx].FileContainerResourceURL[idx+1:] + "?itemPath=artifact-download"
  117. req = NewRequest(t, "GET", url).
  118. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  119. resp = MakeRequest(t, req, http.StatusOK)
  120. var downloadResp downloadArtifactResponse
  121. DecodeJSON(t, resp, &downloadResp)
  122. assert.Len(t, downloadResp.Value, 1)
  123. assert.Equal(t, "artifact-download/abc.txt", downloadResp.Value[0].Path)
  124. assert.Equal(t, "file", downloadResp.Value[0].ItemType)
  125. assert.Contains(t, downloadResp.Value[0].ContentLocation, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
  126. idx = strings.Index(downloadResp.Value[0].ContentLocation, "/api/actions_pipeline/_apis/pipelines/")
  127. url = downloadResp.Value[0].ContentLocation[idx:]
  128. req = NewRequest(t, "GET", url).
  129. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  130. resp = MakeRequest(t, req, http.StatusOK)
  131. body := strings.Repeat("A", 1024)
  132. assert.Equal(t, body, resp.Body.String())
  133. }
  134. func TestActionsArtifactUploadMultipleFile(t *testing.T) {
  135. defer prepareTestEnvActionsArtifacts(t)()
  136. const testArtifactName = "multi-files"
  137. // acquire artifact upload url
  138. req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{
  139. Type: "actions_storage",
  140. Name: testArtifactName,
  141. }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  142. resp := MakeRequest(t, req, http.StatusOK)
  143. var uploadResp uploadArtifactResponse
  144. DecodeJSON(t, resp, &uploadResp)
  145. assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
  146. type uploadingFile struct {
  147. Path string
  148. Content string
  149. MD5 string
  150. }
  151. files := []uploadingFile{
  152. {
  153. Path: "abc-3.txt",
  154. Content: strings.Repeat("D", 1024),
  155. MD5: "9nqj7E8HZmfQtPifCJ5Zww==",
  156. },
  157. {
  158. Path: "xyz/def-2.txt",
  159. Content: strings.Repeat("E", 1024),
  160. MD5: "/s1kKvxeHlUX85vaTaVxuA==",
  161. },
  162. }
  163. for _, f := range files {
  164. // get upload url
  165. idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/")
  166. url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=" + testArtifactName + "/" + f.Path
  167. // upload artifact chunk
  168. req = NewRequestWithBody(t, "PUT", url, strings.NewReader(f.Content)).
  169. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a").
  170. SetHeader("Content-Range", "bytes 0-1023/1024").
  171. SetHeader("x-tfs-filelength", "1024").
  172. SetHeader("x-actions-results-md5", f.MD5) // base64(md5(body))
  173. MakeRequest(t, req, http.StatusOK)
  174. }
  175. t.Logf("Create artifact confirm")
  176. // confirm artifact upload
  177. req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName="+testArtifactName).
  178. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  179. MakeRequest(t, req, http.StatusOK)
  180. }
  181. func TestActionsArtifactDownloadMultiFiles(t *testing.T) {
  182. defer prepareTestEnvActionsArtifacts(t)()
  183. const testArtifactName = "multi-file-download"
  184. req := NewRequest(t, "GET", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts").
  185. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  186. resp := MakeRequest(t, req, http.StatusOK)
  187. var listResp listArtifactsResponse
  188. DecodeJSON(t, resp, &listResp)
  189. assert.Equal(t, int64(2), listResp.Count)
  190. var fileContainerResourceURL string
  191. for _, v := range listResp.Value {
  192. if v.Name == testArtifactName {
  193. fileContainerResourceURL = v.FileContainerResourceURL
  194. break
  195. }
  196. }
  197. assert.Contains(t, fileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
  198. idx := strings.Index(fileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/")
  199. url := fileContainerResourceURL[idx+1:] + "?itemPath=" + testArtifactName
  200. req = NewRequest(t, "GET", url).
  201. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  202. resp = MakeRequest(t, req, http.StatusOK)
  203. var downloadResp downloadArtifactResponse
  204. DecodeJSON(t, resp, &downloadResp)
  205. assert.Len(t, downloadResp.Value, 2)
  206. downloads := [][]string{{"multi-file-download/abc.txt", "B"}, {"multi-file-download/xyz/def.txt", "C"}}
  207. for _, v := range downloadResp.Value {
  208. var bodyChar string
  209. var path string
  210. for _, d := range downloads {
  211. if v.Path == d[0] {
  212. path = d[0]
  213. bodyChar = d[1]
  214. break
  215. }
  216. }
  217. value := v
  218. assert.Equal(t, path, value.Path)
  219. assert.Equal(t, "file", value.ItemType)
  220. assert.Contains(t, value.ContentLocation, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
  221. idx = strings.Index(value.ContentLocation, "/api/actions_pipeline/_apis/pipelines/")
  222. url = value.ContentLocation[idx:]
  223. req = NewRequest(t, "GET", url).
  224. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  225. resp = MakeRequest(t, req, http.StatusOK)
  226. assert.Equal(t, strings.Repeat(bodyChar, 1024), resp.Body.String())
  227. }
  228. }
  229. func TestActionsArtifactUploadWithRetentionDays(t *testing.T) {
  230. defer prepareTestEnvActionsArtifacts(t)()
  231. // acquire artifact upload url
  232. req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{
  233. Type: "actions_storage",
  234. Name: "artifact-retention-days",
  235. RetentionDays: 9,
  236. }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  237. resp := MakeRequest(t, req, http.StatusOK)
  238. var uploadResp uploadArtifactResponse
  239. DecodeJSON(t, resp, &uploadResp)
  240. assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
  241. assert.Contains(t, uploadResp.FileContainerResourceURL, "?retentionDays=9")
  242. // get upload url
  243. idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/")
  244. url := uploadResp.FileContainerResourceURL[idx:] + "&itemPath=artifact-retention-days/abc.txt"
  245. // upload artifact chunk
  246. body := strings.Repeat("A", 1024)
  247. req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)).
  248. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a").
  249. SetHeader("Content-Range", "bytes 0-1023/1024").
  250. SetHeader("x-tfs-filelength", "1024").
  251. SetHeader("x-actions-results-md5", "1HsSe8LeLWh93ILaw1TEFQ==") // base64(md5(body))
  252. MakeRequest(t, req, http.StatusOK)
  253. t.Logf("Create artifact confirm")
  254. // confirm artifact upload
  255. req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-retention-days").
  256. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  257. MakeRequest(t, req, http.StatusOK)
  258. }
  259. func TestActionsArtifactOverwrite(t *testing.T) {
  260. defer prepareTestEnvActionsArtifacts(t)()
  261. {
  262. // download old artifact uploaded by tests above, it should 1024 A
  263. req := NewRequest(t, "GET", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts").
  264. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  265. resp := MakeRequest(t, req, http.StatusOK)
  266. var listResp listArtifactsResponse
  267. DecodeJSON(t, resp, &listResp)
  268. idx := strings.Index(listResp.Value[0].FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/")
  269. url := listResp.Value[0].FileContainerResourceURL[idx+1:] + "?itemPath=artifact-download"
  270. req = NewRequest(t, "GET", url).
  271. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  272. resp = MakeRequest(t, req, http.StatusOK)
  273. var downloadResp downloadArtifactResponse
  274. DecodeJSON(t, resp, &downloadResp)
  275. idx = strings.Index(downloadResp.Value[0].ContentLocation, "/api/actions_pipeline/_apis/pipelines/")
  276. url = downloadResp.Value[0].ContentLocation[idx:]
  277. req = NewRequest(t, "GET", url).
  278. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  279. resp = MakeRequest(t, req, http.StatusOK)
  280. body := strings.Repeat("A", 1024)
  281. assert.Equal(t, resp.Body.String(), body)
  282. }
  283. {
  284. // upload same artifact, it uses 4096 B
  285. req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{
  286. Type: "actions_storage",
  287. Name: "artifact-download",
  288. }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  289. resp := MakeRequest(t, req, http.StatusOK)
  290. var uploadResp uploadArtifactResponse
  291. DecodeJSON(t, resp, &uploadResp)
  292. idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/")
  293. url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=artifact-download/abc.txt"
  294. body := strings.Repeat("B", 4096)
  295. req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)).
  296. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a").
  297. SetHeader("Content-Range", "bytes 0-4095/4096").
  298. SetHeader("x-tfs-filelength", "4096").
  299. SetHeader("x-actions-results-md5", "wUypcJFeZCK5T6r4lfqzqg==") // base64(md5(body))
  300. MakeRequest(t, req, http.StatusOK)
  301. // confirm artifact upload
  302. req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-download").
  303. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  304. MakeRequest(t, req, http.StatusOK)
  305. }
  306. {
  307. // download artifact again, it should 4096 B
  308. req := NewRequest(t, "GET", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts").
  309. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  310. resp := MakeRequest(t, req, http.StatusOK)
  311. var listResp listArtifactsResponse
  312. DecodeJSON(t, resp, &listResp)
  313. var uploadedItem listArtifactsResponseItem
  314. for _, item := range listResp.Value {
  315. if item.Name == "artifact-download" {
  316. uploadedItem = item
  317. break
  318. }
  319. }
  320. assert.Equal(t, "artifact-download", uploadedItem.Name)
  321. idx := strings.Index(uploadedItem.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/")
  322. url := uploadedItem.FileContainerResourceURL[idx+1:] + "?itemPath=artifact-download"
  323. req = NewRequest(t, "GET", url).
  324. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  325. resp = MakeRequest(t, req, http.StatusOK)
  326. var downloadResp downloadArtifactResponse
  327. DecodeJSON(t, resp, &downloadResp)
  328. idx = strings.Index(downloadResp.Value[0].ContentLocation, "/api/actions_pipeline/_apis/pipelines/")
  329. url = downloadResp.Value[0].ContentLocation[idx:]
  330. req = NewRequest(t, "GET", url).
  331. AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
  332. resp = MakeRequest(t, req, http.StatusOK)
  333. body := strings.Repeat("B", 4096)
  334. assert.Equal(t, resp.Body.String(), body)
  335. }
  336. }