package api import ( "bytes" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "chfs-ng/internal/auth" "chfs-ng/internal/storage" ) func webDAVMatrixServer(t *testing.T, mask auth.PermMask) (*Server, string) { t.Helper() root := t.TempDir() for _, name := range []string{"src-copy.txt", "src-move.txt", "src-delete.txt"} { if err := os.WriteFile(filepath.Join(root, name), []byte("src"), 0o644); err != nil { t.Fatal(err) } } return &Server{ Store: storage.Local{Root: root}, Auth: &auth.Manager{ Accounts: map[string]*auth.Account{}, Guest: &auth.Account{Mask: mask}, }, RemoveMode: 3, WebDAVRoot: root, }, root } func webDAVRequest(h http.Handler, method, target string, body []byte, headers map[string]string) int { req := httptest.NewRequest(method, target, bytes.NewReader(body)) for k, v := range headers { req.Header.Set(k, v) } rr := httptest.NewRecorder() h.ServeHTTP(rr, req) return rr.Code } func TestWebDAVPermissionMatrix(t *testing.T) { cases := []struct { name string mask auth.PermMask read int write int del int copy int move int }{ {"none", auth.None, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden}, {"read", auth.Read, http.StatusMultiStatus, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden}, {"read-write", auth.Read | auth.Write, http.StatusMultiStatus, http.StatusCreated, http.StatusForbidden, http.StatusCreated, http.StatusCreated}, {"read-write-delete", auth.Read | auth.Write | auth.Delete, http.StatusMultiStatus, http.StatusCreated, http.StatusNoContent, http.StatusCreated, http.StatusCreated}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { s, root := webDAVMatrixServer(t, tc.mask) h := s.Handler() propf := webDAVRequest(h, "PROPFIND", "/webdav/", nil, map[string]string{"Depth": "1"}) if propf != tc.read { t.Errorf("PROPFIND: got %d want %d", propf, tc.read) } get := webDAVRequest(h, http.MethodGet, "/webdav/src-copy.txt", nil, nil) if get != func() int { if tc.mask.HasRead() { return http.StatusOK } return http.StatusForbidden }() { want := http.StatusForbidden if tc.mask.HasRead() { want = http.StatusOK } t.Errorf("GET: got %d want %d", get, want) } put := webDAVRequest(h, http.MethodPut, "/webdav/new.txt", []byte("new"), nil) if put != tc.write { t.Errorf("PUT: got %d want %d", put, tc.write) } mkcol := webDAVRequest(h, "MKCOL", "/webdav/newdir", nil, nil) if mkcol != tc.write { t.Errorf("MKCOL: got %d want %d", mkcol, tc.write) } del := webDAVRequest(h, http.MethodDelete, "/webdav/src-delete.txt", nil, nil) if del != tc.del { t.Errorf("DELETE: got %d want %d", del, tc.del) } copy := webDAVRequest(h, "COPY", "/webdav/src-copy.txt", nil, map[string]string{"Destination": "/webdav/copied.txt"}) if copy != tc.copy { t.Errorf("COPY: got %d want %d", copy, tc.copy) } move := webDAVRequest(h, "MOVE", "/webdav/src-move.txt", nil, map[string]string{"Destination": "/webdav/moved.txt"}) if move != tc.move { t.Errorf("MOVE: got %d want %d", move, tc.move) } if tc.write == http.StatusCreated { if _, err := os.Stat(filepath.Join(root, "new.txt")); tc.name != "none" && err != nil { t.Errorf("PUT file missing: %v", err) } } }) } }