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 }