github.com/nektos/act@v0.2.63-0.20240520024548-8acde99bfa9c/pkg/artifacts/server_test.go (about)

     1  package artifacts
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  	"testing/fstest"
    15  
    16  	"github.com/julienschmidt/httprouter"
    17  	log "github.com/sirupsen/logrus"
    18  	"github.com/stretchr/testify/assert"
    19  
    20  	"github.com/nektos/act/pkg/model"
    21  	"github.com/nektos/act/pkg/runner"
    22  )
    23  
    24  type writableMapFile struct {
    25  	fstest.MapFile
    26  }
    27  
    28  func (f *writableMapFile) Write(data []byte) (int, error) {
    29  	f.Data = data
    30  	return len(data), nil
    31  }
    32  
    33  func (f *writableMapFile) Close() error {
    34  	return nil
    35  }
    36  
    37  type writeMapFS struct {
    38  	fstest.MapFS
    39  }
    40  
    41  func (fsys writeMapFS) OpenWritable(name string) (WritableFile, error) {
    42  	var file = &writableMapFile{
    43  		MapFile: fstest.MapFile{
    44  			Data: []byte("content2"),
    45  		},
    46  	}
    47  	fsys.MapFS[name] = &file.MapFile
    48  
    49  	return file, nil
    50  }
    51  
    52  func (fsys writeMapFS) OpenAppendable(name string) (WritableFile, error) {
    53  	var file = &writableMapFile{
    54  		MapFile: fstest.MapFile{
    55  			Data: []byte("content2"),
    56  		},
    57  	}
    58  	fsys.MapFS[name] = &file.MapFile
    59  
    60  	return file, nil
    61  }
    62  
    63  func TestNewArtifactUploadPrepare(t *testing.T) {
    64  	assert := assert.New(t)
    65  
    66  	var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
    67  
    68  	router := httprouter.New()
    69  	uploads(router, "artifact/server/path", writeMapFS{memfs})
    70  
    71  	req, _ := http.NewRequest("POST", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
    72  	rr := httptest.NewRecorder()
    73  
    74  	router.ServeHTTP(rr, req)
    75  
    76  	if status := rr.Code; status != http.StatusOK {
    77  		assert.Fail("Wrong status")
    78  	}
    79  
    80  	response := FileContainerResourceURL{}
    81  	err := json.Unmarshal(rr.Body.Bytes(), &response)
    82  	if err != nil {
    83  		panic(err)
    84  	}
    85  
    86  	assert.Equal("http://localhost/upload/1", response.FileContainerResourceURL)
    87  }
    88  
    89  func TestArtifactUploadBlob(t *testing.T) {
    90  	assert := assert.New(t)
    91  
    92  	var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
    93  
    94  	router := httprouter.New()
    95  	uploads(router, "artifact/server/path", writeMapFS{memfs})
    96  
    97  	req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=some/file", strings.NewReader("content"))
    98  	rr := httptest.NewRecorder()
    99  
   100  	router.ServeHTTP(rr, req)
   101  
   102  	if status := rr.Code; status != http.StatusOK {
   103  		assert.Fail("Wrong status")
   104  	}
   105  
   106  	response := ResponseMessage{}
   107  	err := json.Unmarshal(rr.Body.Bytes(), &response)
   108  	if err != nil {
   109  		panic(err)
   110  	}
   111  
   112  	assert.Equal("success", response.Message)
   113  	assert.Equal("content", string(memfs["artifact/server/path/1/some/file"].Data))
   114  }
   115  
   116  func TestFinalizeArtifactUpload(t *testing.T) {
   117  	assert := assert.New(t)
   118  
   119  	var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
   120  
   121  	router := httprouter.New()
   122  	uploads(router, "artifact/server/path", writeMapFS{memfs})
   123  
   124  	req, _ := http.NewRequest("PATCH", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
   125  	rr := httptest.NewRecorder()
   126  
   127  	router.ServeHTTP(rr, req)
   128  
   129  	if status := rr.Code; status != http.StatusOK {
   130  		assert.Fail("Wrong status")
   131  	}
   132  
   133  	response := ResponseMessage{}
   134  	err := json.Unmarshal(rr.Body.Bytes(), &response)
   135  	if err != nil {
   136  		panic(err)
   137  	}
   138  
   139  	assert.Equal("success", response.Message)
   140  }
   141  
   142  func TestListArtifacts(t *testing.T) {
   143  	assert := assert.New(t)
   144  
   145  	var memfs = fstest.MapFS(map[string]*fstest.MapFile{
   146  		"artifact/server/path/1/file.txt": {
   147  			Data: []byte(""),
   148  		},
   149  	})
   150  
   151  	router := httprouter.New()
   152  	downloads(router, "artifact/server/path", memfs)
   153  
   154  	req, _ := http.NewRequest("GET", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
   155  	rr := httptest.NewRecorder()
   156  
   157  	router.ServeHTTP(rr, req)
   158  
   159  	if status := rr.Code; status != http.StatusOK {
   160  		assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
   161  	}
   162  
   163  	response := NamedFileContainerResourceURLResponse{}
   164  	err := json.Unmarshal(rr.Body.Bytes(), &response)
   165  	if err != nil {
   166  		panic(err)
   167  	}
   168  
   169  	assert.Equal(1, response.Count)
   170  	assert.Equal("file.txt", response.Value[0].Name)
   171  	assert.Equal("http://localhost/download/1", response.Value[0].FileContainerResourceURL)
   172  }
   173  
   174  func TestListArtifactContainer(t *testing.T) {
   175  	assert := assert.New(t)
   176  
   177  	var memfs = fstest.MapFS(map[string]*fstest.MapFile{
   178  		"artifact/server/path/1/some/file": {
   179  			Data: []byte(""),
   180  		},
   181  	})
   182  
   183  	router := httprouter.New()
   184  	downloads(router, "artifact/server/path", memfs)
   185  
   186  	req, _ := http.NewRequest("GET", "http://localhost/download/1?itemPath=some/file", nil)
   187  	rr := httptest.NewRecorder()
   188  
   189  	router.ServeHTTP(rr, req)
   190  
   191  	if status := rr.Code; status != http.StatusOK {
   192  		assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
   193  	}
   194  
   195  	response := ContainerItemResponse{}
   196  	err := json.Unmarshal(rr.Body.Bytes(), &response)
   197  	if err != nil {
   198  		panic(err)
   199  	}
   200  
   201  	assert.Equal(1, len(response.Value))
   202  	assert.Equal("some/file", response.Value[0].Path)
   203  	assert.Equal("file", response.Value[0].ItemType)
   204  	assert.Equal("http://localhost/artifact/1/some/file/.", response.Value[0].ContentLocation)
   205  }
   206  
   207  func TestDownloadArtifactFile(t *testing.T) {
   208  	assert := assert.New(t)
   209  
   210  	var memfs = fstest.MapFS(map[string]*fstest.MapFile{
   211  		"artifact/server/path/1/some/file": {
   212  			Data: []byte("content"),
   213  		},
   214  	})
   215  
   216  	router := httprouter.New()
   217  	downloads(router, "artifact/server/path", memfs)
   218  
   219  	req, _ := http.NewRequest("GET", "http://localhost/artifact/1/some/file", nil)
   220  	rr := httptest.NewRecorder()
   221  
   222  	router.ServeHTTP(rr, req)
   223  
   224  	if status := rr.Code; status != http.StatusOK {
   225  		assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
   226  	}
   227  
   228  	data := rr.Body.Bytes()
   229  
   230  	assert.Equal("content", string(data))
   231  }
   232  
   233  type TestJobFileInfo struct {
   234  	workdir               string
   235  	workflowPath          string
   236  	eventName             string
   237  	errorMessage          string
   238  	platforms             map[string]string
   239  	containerArchitecture string
   240  }
   241  
   242  var (
   243  	artifactsPath = path.Join(os.TempDir(), "test-artifacts")
   244  	artifactsAddr = "127.0.0.1"
   245  	artifactsPort = "12345"
   246  )
   247  
   248  func TestArtifactFlow(t *testing.T) {
   249  	if testing.Short() {
   250  		t.Skip("skipping integration test")
   251  	}
   252  
   253  	ctx := context.Background()
   254  
   255  	cancel := Serve(ctx, artifactsPath, artifactsAddr, artifactsPort)
   256  	defer cancel()
   257  
   258  	platforms := map[string]string{
   259  		"ubuntu-latest": "node:16-buster", // Don't use node:16-buster-slim because it doesn't have curl command, which is used in the tests
   260  	}
   261  
   262  	tables := []TestJobFileInfo{
   263  		{"testdata", "upload-and-download", "push", "", platforms, ""},
   264  		{"testdata", "GHSL-2023-004", "push", "", platforms, ""},
   265  	}
   266  	log.SetLevel(log.DebugLevel)
   267  
   268  	for _, table := range tables {
   269  		runTestJobFile(ctx, t, table)
   270  	}
   271  }
   272  
   273  func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) {
   274  	t.Run(tjfi.workflowPath, func(t *testing.T) {
   275  		fmt.Printf("::group::%s\n", tjfi.workflowPath)
   276  
   277  		if err := os.RemoveAll(artifactsPath); err != nil {
   278  			panic(err)
   279  		}
   280  
   281  		workdir, err := filepath.Abs(tjfi.workdir)
   282  		assert.Nil(t, err, workdir)
   283  		fullWorkflowPath := filepath.Join(workdir, tjfi.workflowPath)
   284  		runnerConfig := &runner.Config{
   285  			Workdir:               workdir,
   286  			BindWorkdir:           false,
   287  			EventName:             tjfi.eventName,
   288  			Platforms:             tjfi.platforms,
   289  			ReuseContainers:       false,
   290  			ContainerArchitecture: tjfi.containerArchitecture,
   291  			GitHubInstance:        "github.com",
   292  			ArtifactServerPath:    artifactsPath,
   293  			ArtifactServerAddr:    artifactsAddr,
   294  			ArtifactServerPort:    artifactsPort,
   295  		}
   296  
   297  		runner, err := runner.New(runnerConfig)
   298  		assert.Nil(t, err, tjfi.workflowPath)
   299  
   300  		planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true)
   301  		assert.Nil(t, err, fullWorkflowPath)
   302  
   303  		plan, err := planner.PlanEvent(tjfi.eventName)
   304  		if err == nil {
   305  			err = runner.NewPlanExecutor(plan)(ctx)
   306  			if tjfi.errorMessage == "" {
   307  				assert.Nil(t, err, fullWorkflowPath)
   308  			} else {
   309  				assert.Error(t, err, tjfi.errorMessage)
   310  			}
   311  		} else {
   312  			assert.Nil(t, plan)
   313  		}
   314  
   315  		fmt.Println("::endgroup::")
   316  	})
   317  }
   318  
   319  func TestMkdirFsImplSafeResolve(t *testing.T) {
   320  	assert := assert.New(t)
   321  
   322  	baseDir := "/foo/bar"
   323  
   324  	tests := map[string]struct {
   325  		input string
   326  		want  string
   327  	}{
   328  		"simple":         {input: "baz", want: "/foo/bar/baz"},
   329  		"nested":         {input: "baz/blue", want: "/foo/bar/baz/blue"},
   330  		"dots in middle": {input: "baz/../../blue", want: "/foo/bar/blue"},
   331  		"leading dots":   {input: "../../parent", want: "/foo/bar/parent"},
   332  		"root path":      {input: "/root", want: "/foo/bar/root"},
   333  		"root":           {input: "/", want: "/foo/bar"},
   334  		"empty":          {input: "", want: "/foo/bar"},
   335  	}
   336  
   337  	for name, tc := range tests {
   338  		t.Run(name, func(t *testing.T) {
   339  			assert.Equal(tc.want, safeResolve(baseDir, tc.input))
   340  		})
   341  	}
   342  }
   343  
   344  func TestDownloadArtifactFileUnsafePath(t *testing.T) {
   345  	assert := assert.New(t)
   346  
   347  	var memfs = fstest.MapFS(map[string]*fstest.MapFile{
   348  		"artifact/server/path/some/file": {
   349  			Data: []byte("content"),
   350  		},
   351  	})
   352  
   353  	router := httprouter.New()
   354  	downloads(router, "artifact/server/path", memfs)
   355  
   356  	req, _ := http.NewRequest("GET", "http://localhost/artifact/2/../../some/file", nil)
   357  	rr := httptest.NewRecorder()
   358  
   359  	router.ServeHTTP(rr, req)
   360  
   361  	if status := rr.Code; status != http.StatusOK {
   362  		assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
   363  	}
   364  
   365  	data := rr.Body.Bytes()
   366  
   367  	assert.Equal("content", string(data))
   368  }
   369  
   370  func TestArtifactUploadBlobUnsafePath(t *testing.T) {
   371  	assert := assert.New(t)
   372  
   373  	var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
   374  
   375  	router := httprouter.New()
   376  	uploads(router, "artifact/server/path", writeMapFS{memfs})
   377  
   378  	req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=../../some/file", strings.NewReader("content"))
   379  	rr := httptest.NewRecorder()
   380  
   381  	router.ServeHTTP(rr, req)
   382  
   383  	if status := rr.Code; status != http.StatusOK {
   384  		assert.Fail("Wrong status")
   385  	}
   386  
   387  	response := ResponseMessage{}
   388  	err := json.Unmarshal(rr.Body.Bytes(), &response)
   389  	if err != nil {
   390  		panic(err)
   391  	}
   392  
   393  	assert.Equal("success", response.Message)
   394  	assert.Equal("content", string(memfs["artifact/server/path/1/some/file"].Data))
   395  }