package filewebdav_test import ( "io" "log/slog" "net/http" "net/http/httptest" "os" "strings" "testing" "github.com/yourusername/caldav-server/internal/auth" filewebdav "github.com/yourusername/caldav-server/internal/webdav" ) func testLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) } func doAs(t *testing.T, h http.Handler, user, method, path string, body string) *httptest.ResponseRecorder { t.Helper() req := httptest.NewRequest(method, path, strings.NewReader(body)) ctx := auth.NewContext(req.Context(), &auth.Principal{Username: user}) req = req.WithContext(ctx) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) return rec } // TestPerUserIsolationAndPrefix verifies that all users share the same // "/files/" URL, but each is served from (and can only see) their own // directory on disk, resolved from the Basic Auth identity. func TestPerUserIsolationAndPrefix(t *testing.T) { dir := t.TempDir() h := filewebdav.NewHandler(nil, dir, testLogger()) putRec := doAs(t, h, "alice", http.MethodPut, "/files/note.txt", "hello alice") if putRec.Code != http.StatusCreated && putRec.Code != http.StatusNoContent { t.Fatalf("PUT as alice: unexpected status %d: %s", putRec.Code, putRec.Body.String()) } getRec := doAs(t, h, "alice", http.MethodGet, "/files/note.txt", "") if getRec.Code != http.StatusOK { t.Fatalf("GET as alice: unexpected status %d", getRec.Code) } if getRec.Body.String() != "hello alice" { t.Fatalf("unexpected body: %q", getRec.Body.String()) } // bob hits the exact same URL, but must not see alice's file — his own // directory on disk is empty. bobRec := doAs(t, h, "bob", http.MethodGet, "/files/note.txt", "") if bobRec.Code != http.StatusNotFound { t.Fatalf("expected bob to get 404 for alice's file, got %d", bobRec.Code) } // Confirm the file physically landed under dataDir/alice/files/, matching // the new nested structure. if _, err := os.Stat(dir + "/alice/files/note.txt"); err != nil { t.Fatalf("expected file at dataDir/alice/files/note.txt: %v", err) } } // TestLockPersistsAcrossRequests ensures the LockSystem used by the handler // is not recreated (and thus reset) on every request. func TestLockPersistsAcrossRequests(t *testing.T) { dir := t.TempDir() h := filewebdav.NewHandler(nil, dir, testLogger()) // Create the file first. doAs(t, h, "alice", http.MethodPut, "/files/locked.txt", "v1") lockBody := ` test ` lockRec := doAs(t, h, "alice", "LOCK", "/files/locked.txt", lockBody) if lockRec.Code != http.StatusOK { t.Fatalf("LOCK: unexpected status %d: %s", lockRec.Code, lockRec.Body.String()) } locktoken := lockRec.Header().Get("Lock-Token") if locktoken == "" { t.Fatal("expected Lock-Token header in LOCK response") } // A second, unrelated request must still see the lock as active, // proving the LockSystem instance was reused rather than reset. req := httptest.NewRequest(http.MethodPut, "/files/locked.txt", strings.NewReader("v2 without token")) ctx := auth.NewContext(req.Context(), &auth.Principal{Username: "alice"}) req = req.WithContext(ctx) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) if rec.Code != http.StatusLocked { t.Fatalf("expected 423 Locked for PUT without lock token, got %d: %s", rec.Code, rec.Body.String()) } }