580 lines
18 KiB
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)
|
|
}
|
|
})
|
|
}
|
|
}
|