Files

134 lines
3.2 KiB
Go

package store_test
import (
"fmt"
"os"
"sync"
"testing"
"git.arnef.de/arnef/nidus/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))
}
}
}