396 lines
9.6 KiB
Go
396 lines
9.6 KiB
Go
package artifacts
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
"github.com/julienschmidt/httprouter"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/nektos/act/pkg/model"
|
|
"github.com/nektos/act/pkg/runner"
|
|
)
|
|
|
|
type writableMapFile struct {
|
|
fstest.MapFile
|
|
}
|
|
|
|
func (f *writableMapFile) Write(data []byte) (int, error) {
|
|
f.Data = data
|
|
return len(data), nil
|
|
}
|
|
|
|
func (f *writableMapFile) Close() error {
|
|
return nil
|
|
}
|
|
|
|
type writeMapFS struct {
|
|
fstest.MapFS
|
|
}
|
|
|
|
func (fsys writeMapFS) OpenWritable(name string) (WritableFile, error) {
|
|
var file = &writableMapFile{
|
|
MapFile: fstest.MapFile{
|
|
Data: []byte("content2"),
|
|
},
|
|
}
|
|
fsys.MapFS[name] = &file.MapFile
|
|
|
|
return file, nil
|
|
}
|
|
|
|
func (fsys writeMapFS) OpenAppendable(name string) (WritableFile, error) {
|
|
var file = &writableMapFile{
|
|
MapFile: fstest.MapFile{
|
|
Data: []byte("content2"),
|
|
},
|
|
}
|
|
fsys.MapFS[name] = &file.MapFile
|
|
|
|
return file, nil
|
|
}
|
|
|
|
func TestNewArtifactUploadPrepare(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
|
|
|
|
router := httprouter.New()
|
|
uploads(router, "artifact/server/path", writeMapFS{memfs})
|
|
|
|
req, _ := http.NewRequest("POST", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.Fail("Wrong status")
|
|
}
|
|
|
|
response := FileContainerResourceURL{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal("http://localhost/upload/1", response.FileContainerResourceURL)
|
|
}
|
|
|
|
func TestArtifactUploadBlob(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
|
|
|
|
router := httprouter.New()
|
|
uploads(router, "artifact/server/path", writeMapFS{memfs})
|
|
|
|
req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=some/file", strings.NewReader("content"))
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.Fail("Wrong status")
|
|
}
|
|
|
|
response := ResponseMessage{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal("success", response.Message)
|
|
assert.Equal("content", string(memfs["artifact/server/path/1/some/file"].Data))
|
|
}
|
|
|
|
func TestFinalizeArtifactUpload(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
|
|
|
|
router := httprouter.New()
|
|
uploads(router, "artifact/server/path", writeMapFS{memfs})
|
|
|
|
req, _ := http.NewRequest("PATCH", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.Fail("Wrong status")
|
|
}
|
|
|
|
response := ResponseMessage{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal("success", response.Message)
|
|
}
|
|
|
|
func TestListArtifacts(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{
|
|
"artifact/server/path/1/file.txt": {
|
|
Data: []byte(""),
|
|
},
|
|
})
|
|
|
|
router := httprouter.New()
|
|
downloads(router, "artifact/server/path", memfs)
|
|
|
|
req, _ := http.NewRequest("GET", "http://localhost/_apis/pipelines/workflows/1/artifacts", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
|
|
}
|
|
|
|
response := NamedFileContainerResourceURLResponse{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal(1, response.Count)
|
|
assert.Equal("file.txt", response.Value[0].Name)
|
|
assert.Equal("http://localhost/download/1", response.Value[0].FileContainerResourceURL)
|
|
}
|
|
|
|
func TestListArtifactContainer(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{
|
|
"artifact/server/path/1/some/file": {
|
|
Data: []byte(""),
|
|
},
|
|
})
|
|
|
|
router := httprouter.New()
|
|
downloads(router, "artifact/server/path", memfs)
|
|
|
|
req, _ := http.NewRequest("GET", "http://localhost/download/1?itemPath=some/file", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
|
|
}
|
|
|
|
response := ContainerItemResponse{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal(1, len(response.Value))
|
|
assert.Equal("some/file", response.Value[0].Path)
|
|
assert.Equal("file", response.Value[0].ItemType)
|
|
assert.Equal("http://localhost/artifact/1/some/file/.", response.Value[0].ContentLocation)
|
|
}
|
|
|
|
func TestDownloadArtifactFile(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{
|
|
"artifact/server/path/1/some/file": {
|
|
Data: []byte("content"),
|
|
},
|
|
})
|
|
|
|
router := httprouter.New()
|
|
downloads(router, "artifact/server/path", memfs)
|
|
|
|
req, _ := http.NewRequest("GET", "http://localhost/artifact/1/some/file", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
|
|
}
|
|
|
|
data := rr.Body.Bytes()
|
|
|
|
assert.Equal("content", string(data))
|
|
}
|
|
|
|
type TestJobFileInfo struct {
|
|
workdir string
|
|
workflowPath string
|
|
eventName string
|
|
errorMessage string
|
|
platforms map[string]string
|
|
containerArchitecture string
|
|
}
|
|
|
|
var (
|
|
artifactsPath = path.Join(os.TempDir(), "test-artifacts")
|
|
artifactsAddr = "127.0.0.1"
|
|
artifactsPort = "12345"
|
|
)
|
|
|
|
func TestArtifactFlow(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
cancel := Serve(ctx, artifactsPath, artifactsAddr, artifactsPort)
|
|
defer cancel()
|
|
|
|
platforms := map[string]string{
|
|
"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
|
|
}
|
|
|
|
tables := []TestJobFileInfo{
|
|
{"testdata", "upload-and-download", "push", "", platforms, ""},
|
|
{"testdata", "GHSL-2023-004", "push", "", platforms, ""},
|
|
}
|
|
log.SetLevel(log.DebugLevel)
|
|
|
|
for _, table := range tables {
|
|
runTestJobFile(ctx, t, table)
|
|
}
|
|
}
|
|
|
|
func runTestJobFile(ctx context.Context, t *testing.T, tjfi TestJobFileInfo) {
|
|
t.Run(tjfi.workflowPath, func(t *testing.T) {
|
|
fmt.Printf("::group::%s\n", tjfi.workflowPath)
|
|
|
|
if err := os.RemoveAll(artifactsPath); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
workdir, err := filepath.Abs(tjfi.workdir)
|
|
assert.Nil(t, err, workdir)
|
|
fullWorkflowPath := filepath.Join(workdir, tjfi.workflowPath)
|
|
runnerConfig := &runner.Config{
|
|
Workdir: workdir,
|
|
BindWorkdir: false,
|
|
EventName: tjfi.eventName,
|
|
Platforms: tjfi.platforms,
|
|
ReuseContainers: false,
|
|
ContainerArchitecture: tjfi.containerArchitecture,
|
|
GitHubInstance: "github.com",
|
|
ArtifactServerPath: artifactsPath,
|
|
ArtifactServerAddr: artifactsAddr,
|
|
ArtifactServerPort: artifactsPort,
|
|
}
|
|
|
|
runner, err := runner.New(runnerConfig)
|
|
assert.Nil(t, err, tjfi.workflowPath)
|
|
|
|
planner, err := model.NewWorkflowPlanner(fullWorkflowPath, true)
|
|
assert.Nil(t, err, fullWorkflowPath)
|
|
|
|
plan, err := planner.PlanEvent(tjfi.eventName)
|
|
if err == nil {
|
|
err = runner.NewPlanExecutor(plan)(ctx)
|
|
if tjfi.errorMessage == "" {
|
|
assert.Nil(t, err, fullWorkflowPath)
|
|
} else {
|
|
assert.Error(t, err, tjfi.errorMessage)
|
|
}
|
|
} else {
|
|
assert.Nil(t, plan)
|
|
}
|
|
|
|
fmt.Println("::endgroup::")
|
|
})
|
|
}
|
|
|
|
func TestMkdirFsImplSafeResolve(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
baseDir := "/foo/bar"
|
|
|
|
tests := map[string]struct {
|
|
input string
|
|
want string
|
|
}{
|
|
"simple": {input: "baz", want: "/foo/bar/baz"},
|
|
"nested": {input: "baz/blue", want: "/foo/bar/baz/blue"},
|
|
"dots in middle": {input: "baz/../../blue", want: "/foo/bar/blue"},
|
|
"leading dots": {input: "../../parent", want: "/foo/bar/parent"},
|
|
"root path": {input: "/root", want: "/foo/bar/root"},
|
|
"root": {input: "/", want: "/foo/bar"},
|
|
"empty": {input: "", want: "/foo/bar"},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
assert.Equal(tc.want, safeResolve(baseDir, tc.input))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDownloadArtifactFileUnsafePath(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{
|
|
"artifact/server/path/some/file": {
|
|
Data: []byte("content"),
|
|
},
|
|
})
|
|
|
|
router := httprouter.New()
|
|
downloads(router, "artifact/server/path", memfs)
|
|
|
|
req, _ := http.NewRequest("GET", "http://localhost/artifact/2/../../some/file", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.FailNow(fmt.Sprintf("Wrong status: %d", status))
|
|
}
|
|
|
|
data := rr.Body.Bytes()
|
|
|
|
assert.Equal("content", string(data))
|
|
}
|
|
|
|
func TestArtifactUploadBlobUnsafePath(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
var memfs = fstest.MapFS(map[string]*fstest.MapFile{})
|
|
|
|
router := httprouter.New()
|
|
uploads(router, "artifact/server/path", writeMapFS{memfs})
|
|
|
|
req, _ := http.NewRequest("PUT", "http://localhost/upload/1?itemPath=../../some/file", strings.NewReader("content"))
|
|
rr := httptest.NewRecorder()
|
|
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != http.StatusOK {
|
|
assert.Fail("Wrong status")
|
|
}
|
|
|
|
response := ResponseMessage{}
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
assert.Equal("success", response.Message)
|
|
assert.Equal("content", string(memfs["artifact/server/path/1/some/file"].Data))
|
|
}
|