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