package store_test import ( "fmt" "os" "sync" "testing" "github.com/yourusername/caldav-server/internal/store" ) func TestStoreRoundTrip(t *testing.T) { dir := t.TempDir() st, err := store.NewStore(dir) if err != nil { t.Fatalf("NewStore: %v", err) } const ( user = "alice" col = "cal-personal" objectID = "event-001.ics" ) data := []byte("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n") // Write if err := st.PutObject(user, col, objectID, data); err != nil { t.Fatalf("PutObject: %v", err) } // Read got, err := st.GetObject(user, col, objectID) if err != nil { t.Fatalf("GetObject: %v", err) } if string(got) != string(data) { t.Errorf("data mismatch: got %q, want %q", got, data) } // List ids, err := st.ListObjects(user, col) if err != nil { t.Fatalf("ListObjects: %v", err) } if len(ids) != 1 || ids[0] != objectID { t.Errorf("unexpected ids: %v", ids) } // Delete if err := st.DeleteObject(user, col, objectID); err != nil { t.Fatalf("DeleteObject: %v", err) } // Not found _, err = st.GetObject(user, col, objectID) if err != store.ErrNotFound { t.Errorf("expected ErrNotFound, got %v", err) } } func TestSanitizePath(t *testing.T) { dir := t.TempDir() st, err := store.NewStore(dir) if err != nil { t.Fatalf("NewStore: %v", err) } // Traversal attempts should not escape rootDir dangerous := "../../../etc/passwd" data := []byte("test") if err := st.PutObject("user", "col", dangerous, data); err != nil { t.Fatalf("PutObject with dangerous ID: %v", err) } // Verify file was NOT written outside rootDir _, err = os.Stat("/etc/passwd.tmp") if err == nil { t.Fatal("path traversal succeeded — security issue!") } } // TestConcurrentMultiUserAccess exercises the store from several users // concurrently to make sure the per-user locking not only avoids data races // (checked by -race) but also doesn't serialize unrelated users' operations // incorrectly (e.g. deadlocks or cross-user data corruption). func TestConcurrentMultiUserAccess(t *testing.T) { dir := t.TempDir() st, err := store.NewStore(dir) if err != nil { t.Fatalf("NewStore: %v", err) } const users = 8 const objectsPerUser = 20 var wg sync.WaitGroup for u := 0; u < users; u++ { user := fmt.Sprintf("user%d", u) wg.Add(1) go func(user string) { defer wg.Done() for i := 0; i < objectsPerUser; i++ { id := fmt.Sprintf("obj-%d.ics", i) data := []byte(fmt.Sprintf("DATA-%s-%d", user, i)) if err := st.PutObject(user, "cal-personal", id, data); err != nil { t.Errorf("PutObject(%s, %d): %v", user, i, err) return } got, err := st.GetObject(user, "cal-personal", id) if err != nil { t.Errorf("GetObject(%s, %d): %v", user, i, err) return } if string(got) != string(data) { t.Errorf("cross-user data corruption for %s obj %d: got %q want %q", user, i, got, data) } } }(user) } wg.Wait() for u := 0; u < users; u++ { user := fmt.Sprintf("user%d", u) ids, err := st.ListObjects(user, "cal-personal") if err != nil { t.Fatalf("ListObjects(%s): %v", user, err) } if len(ids) != objectsPerUser { t.Errorf("user %s: expected %d objects, got %d", user, objectsPerUser, len(ids)) } } }