package api import ( "archive/zip" "io" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "chfs-ng/internal/auth" "chfs-ng/internal/storage" ) func TestSharedRoutesMatchCHFS(t *testing.T) { root := t.TempDir() if err := os.WriteFile(filepath.Join(root, "hello.txt"), []byte("hello"), 0o644); err != nil { t.Fatal(err) } s := &Server{Store: storage.Local{Root: root}, Auth: &auth.Manager{Accounts: map[string]*auth.Account{}, Guest: &auth.Account{Mask: auth.Read | auth.Write | auth.Delete}}} r := httptest.NewRequest(http.MethodGet, "/chfs/shared?file=hello.txt", nil) w := httptest.NewRecorder() s.Handler().ServeHTTP(w, r) if w.Code != http.StatusMovedPermanently || w.Header().Get("Location") != "shared/?file=hello.txt" { t.Fatalf("shared redirect: status=%d location=%q", w.Code, w.Header().Get("Location")) } r = httptest.NewRequest(http.MethodGet, "/chfs/shared/hello.txt", nil) w = httptest.NewRecorder() s.Handler().ServeHTTP(w, r) if w.Code != http.StatusOK || w.Header().Get("Content-Disposition") != `attachment;filename="hello.txt"` { t.Fatalf("shared file: status=%d disposition=%q", w.Code, w.Header().Get("Content-Disposition")) } body, _ := io.ReadAll(w.Result().Body) if string(body) != "hello" { t.Fatalf("shared body=%q", body) } } func TestDownloadDirArchiveHeadersAndEntries(t *testing.T) { root := t.TempDir() dir := filepath.Join(root, "pack") if err := os.Mkdir(dir, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "a.txt"), []byte("abc"), 0o644); err != nil { t.Fatal(err) } s := &Server{Store: storage.Local{Root: root}, Auth: &auth.Manager{Accounts: map[string]*auth.Account{}, Guest: &auth.Account{Mask: auth.Read | auth.Write | auth.Delete}}} r := httptest.NewRequest(http.MethodGet, "/chfs/downloaddir/pack", nil) w := httptest.NewRecorder() s.Handler().ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("archive status=%d body=%s", w.Code, w.Body.String()) } if got := w.Header().Get("Content-Type"); got != "application/zip" { t.Fatalf("archive content type=%q", got) } if got := w.Header().Get("Content-Disposition"); got != "" { t.Fatalf("CHFS archive must not emit Content-Disposition, got %q", got) } zr, err := zip.NewReader(bytesReaderAt{r: w.Body.Bytes()}, int64(w.Body.Len())) if err != nil { t.Fatalf("open archive: %v", err) } if len(zr.File) != 1 || zr.File[0].Name != "a.txt" { t.Fatalf("archive entries=%v", archiveNames(zr.File)) } if zr.File[0].UncompressedSize64 != 3 { t.Fatalf("archive size=%d", zr.File[0].UncompressedSize64) } } type bytesReaderAt struct{ r []byte } func (b bytesReaderAt) ReadAt(p []byte, off int64) (int, error) { if off >= int64(len(b.r)) { return 0, io.EOF } n := copy(p, b.r[off:]) if n < len(p) { return n, io.EOF } return n, nil } func archiveNames(files []*zip.File) []string { n := make([]string, 0, len(files)) for _, f := range files { n = append(n, f.Name) } return n }