This commit is contained in:
CFOU
2026-09-14 20:50:19 +02:00
commit 3091fb4020
135 changed files with 10262 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
package users_test
import (
"context"
"errors"
"testing"
"github.com/google/uuid"
"backend/internal/modules/users"
"backend/internal/platform/security"
)
// fakeRepository is an in-memory users.Repository used to unit-test
// users.Service without a real database.
type fakeRepository struct {
byID map[uuid.UUID]*users.User
byEmail map[string]uuid.UUID
}
func newFakeRepository() *fakeRepository {
return &fakeRepository{
byID: make(map[uuid.UUID]*users.User),
byEmail: make(map[string]uuid.UUID),
}
}
func (r *fakeRepository) Create(_ context.Context, u *users.User) error {
if _, exists := r.byEmail[u.Email]; exists {
return users.ErrEmailTaken
}
cp := *u
r.byID[u.ID] = &cp
r.byEmail[u.Email] = u.ID
return nil
}
func (r *fakeRepository) FindByEmail(_ context.Context, email string) (*users.User, error) {
id, ok := r.byEmail[email]
if !ok {
return nil, users.ErrNotFound
}
cp := *r.byID[id]
return &cp, nil
}
func (r *fakeRepository) FindByID(_ context.Context, id uuid.UUID) (*users.User, error) {
u, ok := r.byID[id]
if !ok {
return nil, users.ErrNotFound
}
cp := *u
return &cp, nil
}
func (r *fakeRepository) List(_ context.Context) ([]*users.User, error) {
list := make([]*users.User, 0, len(r.byID))
for _, u := range r.byID {
cp := *u
list = append(list, &cp)
}
return list, nil
}
func (r *fakeRepository) Update(_ context.Context, u *users.User) error {
existing, ok := r.byID[u.ID]
if !ok {
return users.ErrNotFound
}
if existing.Email != u.Email {
if _, taken := r.byEmail[u.Email]; taken {
return users.ErrEmailTaken
}
delete(r.byEmail, existing.Email)
r.byEmail[u.Email] = u.ID
}
cp := *u
r.byID[u.ID] = &cp
return nil
}
func (r *fakeRepository) Delete(_ context.Context, id uuid.UUID) error {
u, ok := r.byID[id]
if !ok {
return users.ErrNotFound
}
delete(r.byEmail, u.Email)
delete(r.byID, id)
return nil
}
func TestService_Create_HashesPasswordAndPersists(t *testing.T) {
svc := users.NewService(newFakeRepository())
ctx := context.Background()
user, err := svc.Create(ctx, "admin@example.com", "super-strong-password", users.RoleAdmin)
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if user.PasswordHash == "super-strong-password" {
t.Fatal("Create() stored the plaintext password instead of a hash")
}
ok, err := security.VerifyPassword(user.PasswordHash, "super-strong-password")
if err != nil || !ok {
t.Fatalf("stored password hash does not verify against the original password (ok=%v, err=%v)", ok, err)
}
}
func TestService_Create_RejectsInvalidRole(t *testing.T) {
svc := users.NewService(newFakeRepository())
if _, err := svc.Create(context.Background(), "a@example.com", "super-strong-password", "superadmin"); err == nil {
t.Fatal("Create() error = nil, want error for an invalid role")
}
}
func TestService_Create_DuplicateEmailRejected(t *testing.T) {
svc := users.NewService(newFakeRepository())
ctx := context.Background()
if _, err := svc.Create(ctx, "dup@example.com", "super-strong-password", users.RoleAdmin); err != nil {
t.Fatalf("first Create() error = %v", err)
}
_, err := svc.Create(ctx, "dup@example.com", "another-strong-password", users.RoleAdmin)
if !errors.Is(err, users.ErrEmailTaken) {
t.Fatalf("second Create() error = %v, want ErrEmailTaken", err)
}
}
func TestService_SetPassword_ChangesHash(t *testing.T) {
svc := users.NewService(newFakeRepository())
ctx := context.Background()
user, err := svc.Create(ctx, "user@example.com", "first-strong-password", users.RoleAdmin)
if err != nil {
t.Fatalf("Create() error = %v", err)
}
oldHash := user.PasswordHash
if err := svc.SetPassword(ctx, user.ID, "second-strong-password"); err != nil {
t.Fatalf("SetPassword() error = %v", err)
}
updated, err := svc.FindByID(ctx, user.ID)
if err != nil {
t.Fatalf("FindByID() error = %v", err)
}
if updated.PasswordHash == oldHash {
t.Fatal("SetPassword() did not change the stored hash")
}
ok, _ := security.VerifyPassword(updated.PasswordHash, "second-strong-password")
if !ok {
t.Fatal("new password does not verify against the updated hash")
}
ok, _ = security.VerifyPassword(updated.PasswordHash, "first-strong-password")
if ok {
t.Fatal("old password still verifies after SetPassword()")
}
}
func TestService_Delete_RemovesUser(t *testing.T) {
svc := users.NewService(newFakeRepository())
ctx := context.Background()
user, err := svc.Create(ctx, "todelete@example.com", "super-strong-password", users.RoleAdmin)
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if err := svc.Delete(ctx, user.ID); err != nil {
t.Fatalf("Delete() error = %v", err)
}
if _, err := svc.FindByID(ctx, user.ID); !errors.Is(err, users.ErrNotFound) {
t.Fatalf("FindByID() after delete error = %v, want ErrNotFound", err)
}
}
func TestService_Delete_UnknownUserReturnsNotFound(t *testing.T) {
svc := users.NewService(newFakeRepository())
if err := svc.Delete(context.Background(), uuid.New()); !errors.Is(err, users.ErrNotFound) {
t.Fatalf("Delete() error = %v, want ErrNotFound", err)
}
}