Files
goyco/internal/services/registration_service_test.go

580 lines
18 KiB
Go

package services
import (
"errors"
"testing"
"time"
"goyco/internal/config"
"goyco/internal/database"
"goyco/internal/testutils"
)
func TestNewRegistrationService(t *testing.T) {
userRepo := testutils.NewMockUserRepository()
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
cfg := testutils.AppTestConfig
service := NewRegistrationService(userRepo, emailService, cfg)
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")
}
if service.config != cfg {
t.Error("expected config to be set")
}
}
func TestRegistrationService_Register(t *testing.T) {
tests := []struct {
name string
username string
email string
password string
setupMocks func() (*testutils.MockUserRepository, *EmailService, *config.Config)
expectedError error
checkResult func(*testing.T, *RegistrationResult)
}{
{
name: "successful registration",
username: "testuser",
email: "test@example.com",
password: "SecurePass123!",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailSender := &testutils.MockEmailSender{}
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: func(t *testing.T, result *RegistrationResult) {
if result == nil {
t.Fatal("expected non-nil result")
}
if result.User == nil {
t.Fatal("expected non-nil user")
}
if result.User.Username != "testuser" {
t.Errorf("expected username 'testuser', got %q", result.User.Username)
}
if result.User.Email != "test@example.com" {
t.Errorf("expected email 'test@example.com', got %q", result.User.Email)
}
if result.User.Password != "" {
t.Error("expected password to be sanitized")
}
if !result.VerificationSent {
t.Error("expected VerificationSent to be true")
}
if result.User.EmailVerified {
t.Error("expected EmailVerified to be false")
}
},
},
{
name: "invalid username",
username: "",
email: "test@example.com",
password: "SecurePass123!",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: nil,
},
{
name: "invalid password",
username: "testuser",
email: "test@example.com",
password: "short",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: nil,
},
{
name: "invalid email",
username: "testuser",
email: "invalid-email",
password: "SecurePass123!",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: nil,
},
{
name: "username already taken",
username: "existinguser",
email: "test@example.com",
password: "SecurePass123!",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
existingUser := &database.User{
ID: 1,
Username: "existinguser",
Email: "existing@example.com",
}
userRepo.Create(existingUser)
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: ErrUsernameTaken,
checkResult: nil,
},
{
name: "email already taken",
username: "testuser",
email: "existing@example.com",
password: "SecurePass123!",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
existingUser := &database.User{
ID: 1,
Username: "existinguser",
Email: "existing@example.com",
}
userRepo.Create(existingUser)
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: ErrEmailTaken,
checkResult: nil,
},
{
name: "email service error",
username: "testuser",
email: "test@example.com",
password: "SecurePass123!",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
errorSender := &errorEmailSender{err: errors.New("email service error")}
emailService, _ := NewEmailService(testutils.AppTestConfig, errorSender)
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: nil,
},
{
name: "trims username whitespace",
username: " testuser ",
email: "test@example.com",
password: "SecurePass123!",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailSender := &testutils.MockEmailSender{}
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: func(t *testing.T, result *RegistrationResult) {
if result.User.Username != "testuser" {
t.Errorf("expected trimmed username 'testuser', got %q", result.User.Username)
}
},
},
{
name: "normalizes email",
username: "testuser",
email: "TEST@EXAMPLE.COM",
password: "SecurePass123!",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailSender := &testutils.MockEmailSender{}
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: func(t *testing.T, result *RegistrationResult) {
if result.User.Email != "test@example.com" {
t.Errorf("expected normalized email 'test@example.com', got %q", result.User.Email)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
userRepo, emailService, cfg := tt.setupMocks()
service := NewRegistrationService(userRepo, emailService, cfg)
result, err := service.Register(tt.username, tt.email, tt.password)
if tt.expectedError != nil {
if err == nil {
t.Fatal("expected error, got nil")
}
if !errors.Is(err, tt.expectedError) {
t.Errorf("expected error %v, got %v", tt.expectedError, err)
}
return
}
if err != nil {
if tt.checkResult == nil {
return
}
t.Fatalf("unexpected error: %v", err)
}
if tt.checkResult != nil {
tt.checkResult(t, result)
}
})
}
}
func TestRegistrationService_ConfirmEmail(t *testing.T) {
tests := []struct {
name string
token string
setupMocks func() (*testutils.MockUserRepository, *EmailService, *config.Config)
expectedError error
checkResult func(*testing.T, *database.User)
}{
{
name: "successful confirmation",
token: "valid-token",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
hashedToken := HashVerificationToken("valid-token")
user := &database.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
EmailVerified: false,
EmailVerificationToken: hashedToken,
}
userRepo.Create(user)
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: func(t *testing.T, user *database.User) {
if user == nil {
t.Fatal("expected non-nil user")
}
if !user.EmailVerified {
t.Error("expected EmailVerified to be true")
}
if user.EmailVerificationToken != "" {
t.Error("expected EmailVerificationToken to be cleared")
}
if user.EmailVerificationSentAt != nil {
t.Error("expected EmailVerificationSentAt to be nil")
}
if user.EmailVerifiedAt == nil {
t.Error("expected EmailVerifiedAt to be set")
}
},
},
{
name: "empty token",
token: "",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: ErrInvalidVerificationToken,
checkResult: nil,
},
{
name: "whitespace only token",
token: " ",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: ErrInvalidVerificationToken,
checkResult: nil,
},
{
name: "invalid token",
token: "invalid-token",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: ErrInvalidVerificationToken,
checkResult: nil,
},
{
name: "already verified",
token: "valid-token",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
hashedToken := HashVerificationToken("valid-token")
now := time.Now()
user := &database.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
EmailVerified: true,
EmailVerifiedAt: &now,
EmailVerificationToken: hashedToken,
}
userRepo.Create(user)
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: func(t *testing.T, user *database.User) {
if user == nil {
t.Fatal("expected non-nil user")
}
if !user.EmailVerified {
t.Error("expected EmailVerified to be true")
}
},
},
{
name: "trims token whitespace",
token: " valid-token ",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
hashedToken := HashVerificationToken("valid-token")
user := &database.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
EmailVerified: false,
EmailVerificationToken: hashedToken,
}
userRepo.Create(user)
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
checkResult: func(t *testing.T, user *database.User) {
if !user.EmailVerified {
t.Error("expected EmailVerified to be true")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
userRepo, emailService, cfg := tt.setupMocks()
service := NewRegistrationService(userRepo, emailService, cfg)
user, err := service.ConfirmEmail(tt.token)
if tt.expectedError != nil {
if err == nil {
t.Fatal("expected error, got nil")
}
if !errors.Is(err, tt.expectedError) {
t.Errorf("expected error %v, got %v", tt.expectedError, err)
}
return
}
if err != nil {
if tt.checkResult == nil {
return
}
t.Fatalf("unexpected error: %v", err)
}
if tt.checkResult != nil {
tt.checkResult(t, user)
}
})
}
}
func TestRegistrationService_ResendVerificationEmail(t *testing.T) {
tests := []struct {
name string
email string
setupMocks func() (*testutils.MockUserRepository, *EmailService, *config.Config)
expectedError error
}{
{
name: "successful resend",
email: "test@example.com",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
oldTime := time.Now().Add(-10 * time.Minute)
user := &database.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
EmailVerified: false,
EmailVerificationSentAt: &oldTime,
}
userRepo.Create(user)
emailSender := &testutils.MockEmailSender{}
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
},
{
name: "invalid email",
email: "invalid-email",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: ErrInvalidEmail,
},
{
name: "user not found",
email: "nonexistent@example.com",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: ErrInvalidCredentials,
},
{
name: "email already verified",
email: "test@example.com",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
now := time.Now()
user := &database.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
EmailVerified: true,
EmailVerifiedAt: &now,
}
userRepo.Create(user)
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
},
{
name: "email sent too recently",
email: "test@example.com",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
recentTime := time.Now().Add(-2 * time.Minute)
user := &database.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
EmailVerified: false,
EmailVerificationSentAt: &recentTime,
}
userRepo.Create(user)
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
},
{
name: "email service error",
email: "test@example.com",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
oldTime := time.Now().Add(-10 * time.Minute)
user := &database.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
EmailVerified: false,
EmailVerificationSentAt: &oldTime,
}
userRepo.Create(user)
errorSender := &errorEmailSender{err: errors.New("email service error")}
emailService, _ := NewEmailService(testutils.AppTestConfig, errorSender)
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
},
{
name: "trims email whitespace",
email: " test@example.com ",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
oldTime := time.Now().Add(-10 * time.Minute)
user := &database.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
EmailVerified: false,
EmailVerificationSentAt: &oldTime,
}
userRepo.Create(user)
emailSender := &testutils.MockEmailSender{}
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
},
{
name: "no previous verification sent",
email: "test@example.com",
setupMocks: func() (*testutils.MockUserRepository, *EmailService, *config.Config) {
userRepo := testutils.NewMockUserRepository()
user := &database.User{
ID: 1,
Username: "testuser",
Email: "test@example.com",
EmailVerified: false,
}
userRepo.Create(user)
emailSender := &testutils.MockEmailSender{}
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
return userRepo, emailService, testutils.AppTestConfig
},
expectedError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
userRepo, emailService, cfg := tt.setupMocks()
service := NewRegistrationService(userRepo, emailService, cfg)
err := service.ResendVerificationEmail(tt.email)
if tt.expectedError != nil {
if err == nil {
t.Fatal("expected error, got nil")
}
if !errors.Is(err, tt.expectedError) {
t.Errorf("expected error %v, got %v", tt.expectedError, err)
}
return
}
if err != nil {
if tt.name == "email already verified" || tt.name == "email sent too recently" || tt.name == "email service error" {
if err.Error() == "" {
t.Fatal("expected error message")
}
return
}
t.Fatalf("unexpected error: %v", err)
}
})
}
}