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