This commit is contained in:
2026-09-09 19:08:30 +07:00
parent 0fb4a6f37b
commit f46ddf19dc
4 changed files with 44 additions and 19 deletions
@@ -6,6 +6,7 @@ import (
"strings"
"testing"
"evening_detective_server/internal/modules/processor_jwt"
"evening_detective_server/internal/repos"
"evening_detective_server/internal/repos/users_repo"
"golang.org/x/crypto/bcrypt"
@@ -61,3 +62,34 @@ func TestChangeUserPassword(t *testing.T) {
})
}
}
// Stop after successful password verification without using a real token database.
type loginPasswordCheck struct {
processor_jwt.IProcessorJWT
err error
}
func (p loginPasswordCheck) GenerateAccessToken(int, string, []string) (string, error) {
return "", p.err
}
func TestLoginAfterPasswordChangePreservesEmailCase(t *testing.T) {
repo := newFakeRepo()
repo.seed("admin@example.com", "unused")
repo.byID[1].Roles = []string{"admin"}
repo.seed("User@Example.COM", "old-hash")
svc := &UsersService{usersRepo: repo, processorJWT: loginPasswordCheck{err: errors.New("stop before token storage")}}
ctx := context.Background()
if err := svc.ChangeUserPassword(ctx, 1, 2, "new password 123"); err != nil {
t.Fatal(err)
}
// This error can only occur after email lookup and bcrypt verification succeed.
_, _, err := svc.Login(ctx, " User@Example.COM ", "new password 123")
if err == nil || err.Error() != "Не получилось получить доступ" {
t.Fatalf("lookup/password verification failed: %v", err)
}
_, _, err = svc.Login(ctx, "user@example.com", "new password 123")
if !errors.Is(err, users_repo.ErrUserNotFound) {
t.Fatalf("email case was changed: %v", err)
}
}
+4 -11
View File
@@ -98,7 +98,7 @@ func (s *UsersService) AddUser(
return ErrTermsNotAccepted
}
email = normalizeEmail(email)
email = strings.TrimSpace(email)
if err := validateEmail(email); err != nil {
return err
}
@@ -182,7 +182,7 @@ func (s *UsersService) RefreshPassword(
ctx context.Context,
email string,
) error {
email = normalizeEmail(email)
email = strings.TrimSpace(email)
if err := validateEmail(email); err != nil {
return err
}
@@ -222,12 +222,6 @@ func (s *UsersService) RefreshPassword(
return s.usersRepo.UpdateUserPassword(ctx, user.Email, string(hashedPassword))
}
// normalizeEmail приводит адрес к каноническому виду (без пробелов, нижний
// регистр): "User@Example.COM " и "user@example.com" — один аккаунт.
func normalizeEmail(email string) string {
return strings.ToLower(strings.TrimSpace(email))
}
// validateEmail проверяет, что строка является одиночным email-адресом.
func validateEmail(email string) error {
if email == "" {
@@ -249,11 +243,10 @@ func (s *UsersService) Login(
email string,
password string,
) (string, string, error) {
// Email нормализуется так же, как при регистрации: иначе пользователь,
// зарегистрировавшийся как user@example.com, не войдёт с User@Example.COM.
// Сохраняем регистр email, как при регистрации; убираем только внешние пробелы.
user, err := s.usersRepo.GetUserByEmail(
ctx,
normalizeEmail(email),
strings.TrimSpace(email),
)
if err != nil {
return "", "", err
@@ -175,17 +175,17 @@ func TestAddUserSuccess(t *testing.T) {
t.Fatalf("AddUser: %v", err)
}
// Email нормализован: пробелы убраны, регистр приведён.
if _, ok := repo.users["user@example.com"]; !ok {
t.Errorf("пользователь не создан с нормализованным email: %+v", repo.users)
// Убираем внешние пробелы, сохраняя исходный регистр email.
if _, ok := repo.users["User@Example.COM"]; !ok {
t.Errorf("пользователь не создан с исходным регистром email: %+v", repo.users)
}
msgs := sender.sent()
if len(msgs) != 1 {
t.Fatalf("ожидалось 1 письмо, отправлено %d", len(msgs))
}
if msgs[0].To != "user@example.com" {
t.Errorf("письмо ушло на %q, want user@example.com", msgs[0].To)
if msgs[0].To != "User@Example.COM" {
t.Errorf("письмо ушло на %q, want User@Example.COM", msgs[0].To)
}
if !strings.Contains(msgs[0].Subject, "Вечернему детективу") {
t.Errorf("неожиданная тема письма: %q", msgs[0].Subject)
@@ -284,11 +284,11 @@ func TestAddUserRequiresTerms(t *testing.T) {
func TestRefreshPasswordSuccess(t *testing.T) {
repo := newFakeRepo()
repo.seed("user@example.com", "old-hash")
repo.seed("User@Example.COM", "old-hash")
sender := &fakeSender{}
svc := newTestService(repo, sender)
// Ввод с пробелами и другим регистром — нормализуется.
// Внешние пробелы убираются, регистр остаётся исходным.
err := svc.RefreshPassword(context.Background(), " User@Example.COM ")
if err != nil {
t.Fatalf("RefreshPassword: %v", err)
@@ -297,7 +297,7 @@ func TestRefreshPasswordSuccess(t *testing.T) {
if len(sender.sent()) != 1 {
t.Fatalf("ожидалось 1 письмо, отправлено %d", len(sender.sent()))
}
u, _ := repo.GetUserByEmail(context.Background(), "user@example.com")
u, _ := repo.GetUserByEmail(context.Background(), "User@Example.COM")
if u.PasswordHash == "old-hash" {
t.Error("пароль не изменён")
}