package storage import ( "context" "errors" "path/filepath" "testing" "time" ) func newTestStore(t *testing.T) *UserStore { t.Helper() dir := t.TempDir() db, err := Open(filepath.Join(dir, "test.db")) if err != nil { t.Fatalf("open: %v", err) } t.Cleanup(func() { _ = db.Close() }) return NewUserStore(db) } func TestCreateAndGet(t *testing.T) { s := newTestStore(t) ctx := context.Background() u, err := s.Create(ctx, User{Email: "Alice@example.COM", StoragePath: "data/alice"}) if err != nil { t.Fatalf("create: %v", err) } if u.ID == "" { t.Fatal("expected generated ID") } if u.Email != "alice@example.com" { t.Errorf("email not normalised: %q", u.Email) } got, err := s.GetByID(ctx, u.ID) if err != nil { t.Fatalf("get by id: %v", err) } if got.Email != u.Email || got.StoragePath != u.StoragePath { t.Errorf("mismatch: %+v vs %+v", got, u) } got2, err := s.GetByEmail(ctx, " ALICE@example.com ") if err != nil { t.Fatalf("get by email: %v", err) } if got2.ID != u.ID { t.Errorf("email lookup id mismatch") } } func TestEmailUniqueness(t *testing.T) { s := newTestStore(t) ctx := context.Background() _, err := s.Create(ctx, User{Email: "dup@example.com", StoragePath: "data/dup"}) if err != nil { t.Fatalf("first create: %v", err) } _, err = s.Create(ctx, User{Email: "DUP@example.com", StoragePath: "data/dup2"}) if !errors.Is(err, ErrEmailTaken) { t.Fatalf("expected ErrEmailTaken, got %v", err) } } func TestUpdateLastLogin(t *testing.T) { s := newTestStore(t) ctx := context.Background() u, err := s.Create(ctx, User{Email: "login@example.com", StoragePath: "data/login"}) if err != nil { t.Fatal(err) } if u.LastLoginAt != nil { t.Errorf("expected nil last_login on create") } now := time.Now().UTC().Truncate(time.Second) if err := s.UpdateLastLogin(ctx, u.ID, now); err != nil { t.Fatalf("update: %v", err) } got, _ := s.GetByID(ctx, u.ID) if got.LastLoginAt == nil || !got.LastLoginAt.Equal(now) { t.Errorf("last_login mismatch: %v vs %v", got.LastLoginAt, now) } } func TestUpdateLastLoginMissing(t *testing.T) { s := newTestStore(t) err := s.UpdateLastLogin(context.Background(), "nope", time.Now()) if !errors.Is(err, ErrNotFound) { t.Errorf("expected ErrNotFound, got %v", err) } } func TestDelete(t *testing.T) { s := newTestStore(t) ctx := context.Background() u, _ := s.Create(ctx, User{Email: "del@example.com", StoragePath: "data/del"}) if err := s.Delete(ctx, u.ID); err != nil { t.Fatalf("delete: %v", err) } _, err := s.GetByID(ctx, u.ID) if !errors.Is(err, ErrNotFound) { t.Errorf("expected ErrNotFound after delete, got %v", err) } if err := s.Delete(ctx, u.ID); !errors.Is(err, ErrNotFound) { t.Errorf("expected ErrNotFound on second delete, got %v", err) } } func TestGetMissing(t *testing.T) { s := newTestStore(t) if _, err := s.GetByID(context.Background(), "missing"); !errors.Is(err, ErrNotFound) { t.Errorf("got %v", err) } if _, err := s.GetByEmail(context.Background(), "nobody@example.com"); !errors.Is(err, ErrNotFound) { t.Errorf("got %v", err) } } func TestWALMode(t *testing.T) { dir := t.TempDir() db, err := Open(filepath.Join(dir, "wal.db")) if err != nil { t.Fatal(err) } defer db.Close() var mode string if err := db.QueryRow("PRAGMA journal_mode").Scan(&mode); err != nil { t.Fatal(err) } if mode != "wal" { t.Errorf("expected WAL mode, got %q", mode) } }