package server_test import ( "net/http" "net/url" "os" "path/filepath" "strings" "testing" "g.fc.run/misc/byre/internal/api" ) func TestTaskFiles(t *testing.T) { h := newHarness(t) p := h.createProject("demo") task := api.Task{} h.do("POST", "/api/projects/"+p.Id+"/tasks", api.TaskCreate{Title: "files"}, &task) wt := task.WorktreePath outside := t.TempDir() writeFile(t, filepath.Join(outside, "secret"), "secret\n") err := os.Mkdir(filepath.Join(wt, "src"), 0o755) if err != nil { t.Fatalf("mkdir: %v", err) } writeFile(t, filepath.Join(wt, "src", "main.go"), "package main\n") writeFile(t, filepath.Join(wt, "src", "main.o"), "obj") writeFile(t, filepath.Join(wt, ".gitignore"), "*.o\n/out/\n") err = os.Mkdir(filepath.Join(wt, "out"), 0o755) if err != nil { t.Fatalf("mkdir: %v", err) } writeFile(t, filepath.Join(wt, "out", "app"), "bin") writeFile(t, filepath.Join(wt, "blob.bin"), "a\x00b") writeFile(t, filepath.Join(wt, "big.txt"), strings.Repeat("é", 200_000)) for name, target := range map[string]string{ "inlink": "src", "outlink": filepath.Join(outside, "secret"), "outdir": outside, "dangling": "nope", } { err = os.Symlink(target, filepath.Join(wt, name)) if err != nil { t.Fatalf("symlink: %v", err) } } dir := func(path string) (api.DirListing, int) { l := api.DirListing{} code := h.do("GET", "/api/tasks/"+task.Id+"/dir?path="+url.QueryEscape(path), nil, &l) return l, code } file := func(path string) (api.FileContent, int) { f := api.FileContent{} code := h.do("GET", "/api/tasks/"+task.Id+"/file?path="+url.QueryEscape(path), nil, &f) return f, code } root, code := dir("") if code != http.StatusOK { t.Fatalf("list root: %d", code) } names := func(l api.DirListing) string { got := []string{} for _, e := range l.Entries { s := string(e.Type) + ":" + e.Name if e.Ignored { s += "!" } got = append(got, s) } return strings.Join(got, " ") } want := "dir:inlink dir:out! dir:src file:.gitignore file:README file:big.txt file:blob.bin other:dangling other:outdir other:outlink" if names(root) != want { t.Fatalf("root listing %q, want %q", names(root), want) } for path, want := range map[string]string{ "src": "file:main.go file:main.o!", "inlink": "file:main.go file:main.o!", "out": "file:app!", } { sub, code := dir(path) if code != http.StatusOK || sub.Path != path || names(sub) != want { t.Fatalf("list %s: %d %q, want %q", path, code, names(sub), want) } } f, code := file("src/main.go") if code != http.StatusOK || f.Text != "package main\n" || f.Binary || f.Truncated { t.Fatalf("read text: %d %+v", code, f) } f, code = file("blob.bin") if code != http.StatusOK || !f.Binary || f.Text != "" || f.Size != 3 { t.Fatalf("read binary: %d %+v", code, f) } f, code = file("big.txt") if code != http.StatusOK || f.Binary || !f.Truncated || f.Size != 400_000 || len(f.Text) != 256<<10 { t.Fatalf("read big: %d binary=%v truncated=%v size=%d len=%d", code, f.Binary, f.Truncated, f.Size, len(f.Text)) } for _, path := range []string{"../secret", "/etc/passwd", "src/../../x", "outlink", "outdir/secret", ".git", "src"} { _, code = file(path) if code != http.StatusBadRequest { t.Fatalf("read %s: want 400, got %d", path, code) } } for _, path := range []string{"..", "outdir", "README"} { _, code = dir(path) if code != http.StatusBadRequest { t.Fatalf("list %s: want 400, got %d", path, code) } } _, code = file("missing") if code != http.StatusNotFound { t.Fatalf("read missing: want 404, got %d", code) } }