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  }