package browser import ( "errors" "os" "path/filepath" "testing" "webfilebrowser/internal/config" ) func TestListReadsAConfiguredRootAndNestedDirectory(t *testing.T) { rootPath := t.TempDir() if err := os.Mkdir(filepath.Join(rootPath, "documents"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(rootPath, "documents", "report.txt"), []byte("report"), 0o600); err != nil { t.Fatal(err) } browser, err := New([]config.Root{{Name: "Media", Path: rootPath}}) if err != nil { t.Fatalf("New() error = %v", err) } directory, err := browser.List("Media", "documents") if err != nil { t.Fatalf("List() error = %v", err) } if directory.RelativePath != "documents" { t.Errorf("List().RelativePath = %q, want %q", directory.RelativePath, "documents") } if len(directory.Entries) != 1 { t.Fatalf("List().Entries length = %d, want 1", len(directory.Entries)) } entry := directory.Entries[0] if entry.Name != "report.txt" || entry.IsDirectory || entry.Size != int64(len("report")) { t.Errorf("List().Entries[0] = %#v, want report.txt file with its byte size", entry) } } func TestListRejectsTraversalAndSymlinkEscapes(t *testing.T) { rootPath := t.TempDir() outsidePath := t.TempDir() if err := os.WriteFile(filepath.Join(outsidePath, "secret.txt"), []byte("outside-root"), 0o600); err != nil { t.Fatal(err) } if err := os.Symlink(outsidePath, filepath.Join(rootPath, "escape")); err != nil { t.Fatal(err) } browser, err := New([]config.Root{{Name: "Media", Path: rootPath}}) if err != nil { t.Fatal(err) } for _, relativePath := range []string{"../" + filepath.Base(outsidePath), "escape"} { t.Run(relativePath, func(t *testing.T) { if _, err := browser.List("Media", relativePath); !errors.Is(err, ErrPathOutsideRoot) { t.Errorf("List(%q) error = %v, want ErrPathOutsideRoot", relativePath, err) } }) } }