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_actions_artifact_test.go 15KB

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