418 lines
11 KiB
Go
418 lines
11 KiB
Go
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")
|
|
}
|
|
}
|