165 lines
5.9 KiB
Go
165 lines
5.9 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/user"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestResetPasswordUseCase(t *testing.T) {
|
|
newRepo := func(expiration time.Time) *mockUserRepo {
|
|
repo := newMockUserRepo()
|
|
repo.users[3] = user.User{
|
|
ID: 3,
|
|
Email: "alice@example.com",
|
|
PasswordResetCode: "hashed:reset-code-1",
|
|
PasswordResetExpiration: expiration,
|
|
}
|
|
return repo
|
|
}
|
|
|
|
t.Run("The use case itself sets the password and clears the code in one write, independently of any handler", func(t *testing.T) {
|
|
repo := newRepo(time.Now().Add(time.Hour))
|
|
uc := useCases.NewResetPasswordUseCase(repo, &stubHasher{})
|
|
|
|
err := uc.ResetPassword("alice@example.com", "reset-code-1", "newpassword1")
|
|
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
if repo.resetPasswordCalls != 1 {
|
|
t.Fatalf("expected ResetPassword to be called exactly once, got %d", repo.resetPasswordCalls)
|
|
}
|
|
if repo.lastResetPasswordUserId != 3 {
|
|
t.Errorf("expected userId 3, got %d", repo.lastResetPasswordUserId)
|
|
}
|
|
if repo.lastResetPasswordHash != "hashed:newpassword1" {
|
|
t.Errorf("expected hash %q, got %q", "hashed:newpassword1", repo.lastResetPasswordHash)
|
|
}
|
|
})
|
|
|
|
t.Run("The use case rejects a wrong code without touching the repository's ResetPassword method", func(t *testing.T) {
|
|
repo := newRepo(time.Now().Add(time.Hour))
|
|
uc := useCases.NewResetPasswordUseCase(repo, &stubHasher{})
|
|
|
|
err := uc.ResetPassword("alice@example.com", "wrong-code", "newpassword1")
|
|
|
|
if !errors.Is(err, user.ErrInvalidPasswordResetCode) {
|
|
t.Errorf("expected user.ErrInvalidPasswordResetCode, got %v", err)
|
|
}
|
|
if repo.resetPasswordCalls != 0 {
|
|
t.Errorf("expected ResetPassword to never be called, got %d", repo.resetPasswordCalls)
|
|
}
|
|
})
|
|
|
|
t.Run("The use case rejects an expired code without touching the repository's ResetPassword method", func(t *testing.T) {
|
|
repo := newRepo(time.Now().Add(-time.Hour))
|
|
uc := useCases.NewResetPasswordUseCase(repo, &stubHasher{})
|
|
|
|
err := uc.ResetPassword("alice@example.com", "reset-code-1", "newpassword1")
|
|
|
|
if !errors.Is(err, user.ErrPasswordResetCodeExpired) {
|
|
t.Errorf("expected user.ErrPasswordResetCodeExpired, got %v", err)
|
|
}
|
|
if repo.resetPasswordCalls != 0 {
|
|
t.Errorf("expected ResetPassword to never be called, got %d", repo.resetPasswordCalls)
|
|
}
|
|
})
|
|
|
|
t.Run("The use case treats an unknown email the same as a wrong code", func(t *testing.T) {
|
|
repo := newMockUserRepo()
|
|
uc := useCases.NewResetPasswordUseCase(repo, &stubHasher{})
|
|
|
|
err := uc.ResetPassword("nobody@example.com", "any-code", "newpassword1")
|
|
|
|
if !errors.Is(err, user.ErrInvalidPasswordResetCode) {
|
|
t.Errorf("expected user.ErrInvalidPasswordResetCode, got %v", err)
|
|
}
|
|
if repo.resetPasswordCalls != 0 {
|
|
t.Errorf("expected ResetPassword to never be called, got %d", repo.resetPasswordCalls)
|
|
}
|
|
})
|
|
|
|
t.Run("The use case treats an empty stored code the same as a wrong code, never calling Compare with an empty hash", func(t *testing.T) {
|
|
repo := newMockUserRepo()
|
|
repo.users[3] = user.User{ID: 3, Email: "alice@example.com", PasswordResetCode: ""}
|
|
hasher := &stubHasher{}
|
|
uc := useCases.NewResetPasswordUseCase(repo, hasher)
|
|
|
|
err := uc.ResetPassword("alice@example.com", "anything", "newpassword1")
|
|
|
|
if !errors.Is(err, user.ErrInvalidPasswordResetCode) {
|
|
t.Errorf("expected user.ErrInvalidPasswordResetCode, got %v", err)
|
|
}
|
|
if repo.resetPasswordCalls != 0 {
|
|
t.Errorf("expected ResetPassword to never be called, got %d", repo.resetPasswordCalls)
|
|
}
|
|
if hasher.compareCalls != 0 {
|
|
t.Errorf("expected Compare to never be called, got %d calls", hasher.compareCalls)
|
|
}
|
|
})
|
|
|
|
t.Run("The use case treats any GetByEmail failure, not just not-found, as an invalid code", func(t *testing.T) {
|
|
repo := newRepo(time.Now().Add(time.Hour))
|
|
repo.fail.failNextWith("GetByEmail", errors.New("db is down"))
|
|
uc := useCases.NewResetPasswordUseCase(repo, &stubHasher{})
|
|
|
|
err := uc.ResetPassword("alice@example.com", "reset-code-1", "newpassword1")
|
|
|
|
if !errors.Is(err, user.ErrInvalidPasswordResetCode) {
|
|
t.Errorf("expected user.ErrInvalidPasswordResetCode, got %v", err)
|
|
}
|
|
if repo.resetPasswordCalls != 0 {
|
|
t.Errorf("expected ResetPassword to never be called, got %d", repo.resetPasswordCalls)
|
|
}
|
|
})
|
|
|
|
t.Run("A hashing failure propagates and the repository's ResetPassword method is never called", func(t *testing.T) {
|
|
repo := newRepo(time.Now().Add(time.Hour))
|
|
hashErr := errors.New("hash blew up")
|
|
uc := useCases.NewResetPasswordUseCase(repo, &stubHasher{hashErr: hashErr})
|
|
|
|
err := uc.ResetPassword("alice@example.com", "reset-code-1", "newpassword1")
|
|
|
|
if !errors.Is(err, hashErr) {
|
|
t.Errorf("expected %v, got %v", hashErr, err)
|
|
}
|
|
if repo.resetPasswordCalls != 0 {
|
|
t.Errorf("expected ResetPassword to never be called, got %d", repo.resetPasswordCalls)
|
|
}
|
|
})
|
|
|
|
t.Run("A failure in the repository's ResetPassword method propagates", func(t *testing.T) {
|
|
repo := newRepo(time.Now().Add(time.Hour))
|
|
repo.fail.failNext("ResetPassword")
|
|
uc := useCases.NewResetPasswordUseCase(repo, &stubHasher{})
|
|
|
|
err := uc.ResetPassword("alice@example.com", "reset-code-1", "newpassword1")
|
|
|
|
if !errors.Is(err, errRepositoryFailure) {
|
|
t.Errorf("expected %v, got %v", errRepositoryFailure, err)
|
|
}
|
|
})
|
|
|
|
t.Run("The generic reset-code update stores an empty code as empty instead of hashing it", func(t *testing.T) {
|
|
repo := newMockUserRepo()
|
|
repo.users[3] = user.User{ID: 3, Email: "alice@example.com"}
|
|
hasher := &stubHasher{}
|
|
uc := useCases.NewUpdateUserUseCase(repo, hasher)
|
|
|
|
_, err := uc.UpdatePasswordResetCode(3, "", time.Time{})
|
|
|
|
if err != nil {
|
|
t.Fatalf("expected no error, got %v", err)
|
|
}
|
|
if hasher.hashCalls != 0 {
|
|
t.Errorf("expected Hash to never be called, got %d calls", hasher.hashCalls)
|
|
}
|
|
if repo.users[3].PasswordResetCode != "" {
|
|
t.Errorf("expected stored PasswordResetCode to be empty, got %q", repo.users[3].PasswordResetCode)
|
|
}
|
|
})
|
|
}
|