To gitea and beyond, let's go(-yco)
This commit is contained in:
417
internal/services/password_reset_service_test.go
Normal file
417
internal/services/password_reset_service_test.go
Normal file
@@ -0,0 +1,417 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"goyco/internal/database"
|
||||
"goyco/internal/testutils"
|
||||
)
|
||||
|
||||
func TestNewPasswordResetService(t *testing.T) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
|
||||
|
||||
service := NewPasswordResetService(userRepo, emailService)
|
||||
|
||||
if service == nil {
|
||||
t.Fatal("expected service to be created")
|
||||
}
|
||||
|
||||
if service.userRepo != userRepo {
|
||||
t.Error("expected userRepo to be set")
|
||||
}
|
||||
|
||||
if service.emailService != emailService {
|
||||
t.Error("expected emailService to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordResetService_RequestPasswordReset(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
usernameOrEmail string
|
||||
setupMocks func() (*testutils.MockUserRepository, EmailSender)
|
||||
expectedError bool
|
||||
shouldSendEmail bool
|
||||
}{
|
||||
{
|
||||
name: "successful request by username",
|
||||
usernameOrEmail: "testuser",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
emailSender := &testutils.MockEmailSender{}
|
||||
|
||||
return userRepo, emailSender
|
||||
},
|
||||
expectedError: false,
|
||||
shouldSendEmail: true,
|
||||
},
|
||||
{
|
||||
name: "successful request by email",
|
||||
usernameOrEmail: "test@example.com",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
emailSender := &testutils.MockEmailSender{}
|
||||
|
||||
return userRepo, emailSender
|
||||
},
|
||||
expectedError: false,
|
||||
shouldSendEmail: true,
|
||||
},
|
||||
{
|
||||
name: "empty input",
|
||||
usernameOrEmail: "",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
shouldSendEmail: false,
|
||||
},
|
||||
{
|
||||
name: "whitespace only input",
|
||||
usernameOrEmail: " ",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
shouldSendEmail: false,
|
||||
},
|
||||
{
|
||||
name: "user not found",
|
||||
usernameOrEmail: "nonexistent",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: false,
|
||||
shouldSendEmail: false,
|
||||
},
|
||||
{
|
||||
name: "email service error",
|
||||
usernameOrEmail: "testuser",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
var errorSender errorEmailSender
|
||||
errorSender.err = errors.New("email service error")
|
||||
emailSender := &errorSender
|
||||
|
||||
return userRepo, emailSender
|
||||
},
|
||||
expectedError: true,
|
||||
shouldSendEmail: false,
|
||||
},
|
||||
{
|
||||
name: "prefers email over username",
|
||||
usernameOrEmail: "test@example.com",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
emailSender := &testutils.MockEmailSender{}
|
||||
|
||||
return userRepo, emailSender
|
||||
},
|
||||
expectedError: false,
|
||||
shouldSendEmail: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
userRepo, emailSender := tt.setupMocks()
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
|
||||
|
||||
service := NewPasswordResetService(userRepo, emailService)
|
||||
|
||||
err := service.RequestPasswordReset(tt.usernameOrEmail)
|
||||
|
||||
if tt.expectedError {
|
||||
if err == nil {
|
||||
t.Error("expected error but got none")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if tt.shouldSendEmail {
|
||||
user, _ := userRepo.GetByUsername("testuser")
|
||||
if user == nil {
|
||||
user, _ = userRepo.GetByEmail("test@example.com")
|
||||
}
|
||||
if user != nil && user.PasswordResetToken == "" {
|
||||
t.Error("expected password reset token to be set")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordResetService_ResetPassword(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
newPassword string
|
||||
setupMocks func() (*testutils.MockUserRepository, EmailSender)
|
||||
expectedError bool
|
||||
verifyPassword bool
|
||||
}{
|
||||
{
|
||||
name: "successful password reset",
|
||||
token: "valid-token",
|
||||
newPassword: "NewSecurePass123!",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
expiresAt := time.Now().Add(time.Hour)
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
PasswordResetToken: HashVerificationToken("valid-token"),
|
||||
PasswordResetExpiresAt: &expiresAt,
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
return userRepo, &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: false,
|
||||
verifyPassword: true,
|
||||
},
|
||||
{
|
||||
name: "empty token",
|
||||
token: "",
|
||||
newPassword: "NewSecurePass123!",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
verifyPassword: false,
|
||||
},
|
||||
{
|
||||
name: "whitespace only token",
|
||||
token: " ",
|
||||
newPassword: "NewSecurePass123!",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
verifyPassword: false,
|
||||
},
|
||||
{
|
||||
name: "invalid token",
|
||||
token: "invalid-token",
|
||||
newPassword: "NewSecurePass123!",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
verifyPassword: false,
|
||||
},
|
||||
{
|
||||
name: "expired token",
|
||||
token: "expired-token",
|
||||
newPassword: "NewSecurePass123!",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
expiresAt := time.Now().Add(-time.Hour)
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
PasswordResetToken: HashVerificationToken("expired-token"),
|
||||
PasswordResetExpiresAt: &expiresAt,
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
return userRepo, &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
verifyPassword: false,
|
||||
},
|
||||
{
|
||||
name: "nil expiration date",
|
||||
token: "valid-token",
|
||||
newPassword: "NewSecurePass123!",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
PasswordResetToken: HashVerificationToken("valid-token"),
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
return userRepo, &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
verifyPassword: false,
|
||||
},
|
||||
{
|
||||
name: "invalid password",
|
||||
token: "valid-token",
|
||||
newPassword: "short",
|
||||
setupMocks: func() (*testutils.MockUserRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
expiresAt := time.Now().Add(time.Hour)
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
PasswordResetToken: HashVerificationToken("valid-token"),
|
||||
PasswordResetExpiresAt: &expiresAt,
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
return userRepo, &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
verifyPassword: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
userRepo, emailSender := tt.setupMocks()
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
|
||||
|
||||
service := NewPasswordResetService(userRepo, emailService)
|
||||
|
||||
err := service.ResetPassword(tt.token, tt.newPassword)
|
||||
|
||||
if tt.expectedError {
|
||||
if err == nil {
|
||||
t.Error("expected error but got none")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if tt.verifyPassword {
|
||||
user, _ := userRepo.GetByUsername("testuser")
|
||||
if user == nil {
|
||||
t.Fatal("expected user to exist")
|
||||
}
|
||||
|
||||
if user.PasswordResetToken != "" {
|
||||
t.Error("expected password reset token to be cleared")
|
||||
}
|
||||
|
||||
if user.PasswordResetExpiresAt != nil {
|
||||
t.Error("expected password reset expiration to be cleared")
|
||||
}
|
||||
|
||||
if user.Password == "" {
|
||||
t.Error("expected password to be set")
|
||||
}
|
||||
|
||||
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(tt.newPassword))
|
||||
if err != nil {
|
||||
t.Errorf("password hash verification failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordResetService_ResetPassword_TokenClearedAfterExpiration(t *testing.T) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
expiresAt := time.Now().Add(-time.Hour)
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
PasswordResetToken: HashVerificationToken("expired-token"),
|
||||
PasswordResetExpiresAt: &expiresAt,
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
|
||||
service := NewPasswordResetService(userRepo, emailService)
|
||||
|
||||
err := service.ResetPassword("expired-token", "NewSecurePass123!")
|
||||
if err == nil {
|
||||
t.Error("expected error for expired token")
|
||||
}
|
||||
|
||||
updatedUser, _ := userRepo.GetByID(1)
|
||||
if updatedUser == nil {
|
||||
t.Fatal("expected user to exist")
|
||||
}
|
||||
|
||||
if updatedUser.PasswordResetToken != "" {
|
||||
t.Error("expected password reset token to be cleared after expiration")
|
||||
}
|
||||
|
||||
if updatedUser.PasswordResetExpiresAt != nil {
|
||||
t.Error("expected password reset expiration to be cleared after expiration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordResetService_RequestPasswordReset_EmailFailureRollback(t *testing.T) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
userRepo.Create(user)
|
||||
|
||||
emailSender := &errorEmailSender{err: errors.New("email service error")}
|
||||
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
|
||||
service := NewPasswordResetService(userRepo, emailService)
|
||||
|
||||
err := service.RequestPasswordReset("testuser")
|
||||
if err == nil {
|
||||
t.Error("expected error when email fails")
|
||||
}
|
||||
|
||||
updatedUser, _ := userRepo.GetByID(1)
|
||||
if updatedUser == nil {
|
||||
t.Fatal("expected user to exist")
|
||||
}
|
||||
|
||||
if updatedUser.PasswordResetToken != "" {
|
||||
t.Error("expected password reset token to be rolled back on email failure")
|
||||
}
|
||||
|
||||
if updatedUser.PasswordResetSentAt != nil {
|
||||
t.Error("expected password reset sent at to be rolled back on email failure")
|
||||
}
|
||||
|
||||
if updatedUser.PasswordResetExpiresAt != nil {
|
||||
t.Error("expected password reset expiration to be rolled back on email failure")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user