To gitea and beyond, let's go(-yco)
This commit is contained in:
529
internal/services/account_deletion_service_test.go
Normal file
529
internal/services/account_deletion_service_test.go
Normal file
@@ -0,0 +1,529 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"goyco/internal/database"
|
||||
"goyco/internal/testutils"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type errorEmailSender struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *errorEmailSender) Send(to, subject, body string) error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
type mockAccountDeletionRepository struct {
|
||||
requests map[uint]*database.AccountDeletionRequest
|
||||
requestsByTokenHash map[string]*database.AccountDeletionRequest
|
||||
nextID uint
|
||||
createErr error
|
||||
getByTokenHashErr error
|
||||
deleteByIDErr error
|
||||
deleteByUserIDErr error
|
||||
}
|
||||
|
||||
func newMockAccountDeletionRepository() *mockAccountDeletionRepository {
|
||||
return &mockAccountDeletionRepository{
|
||||
requests: make(map[uint]*database.AccountDeletionRequest),
|
||||
requestsByTokenHash: make(map[string]*database.AccountDeletionRequest),
|
||||
nextID: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockAccountDeletionRepository) Create(req *database.AccountDeletionRequest) error {
|
||||
if m.createErr != nil {
|
||||
return m.createErr
|
||||
}
|
||||
|
||||
req.ID = m.nextID
|
||||
m.nextID++
|
||||
|
||||
reqCopy := *req
|
||||
m.requests[req.ID] = &reqCopy
|
||||
m.requestsByTokenHash[req.TokenHash] = &reqCopy
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAccountDeletionRepository) GetByTokenHash(hash string) (*database.AccountDeletionRequest, error) {
|
||||
if m.getByTokenHashErr != nil {
|
||||
return nil, m.getByTokenHashErr
|
||||
}
|
||||
|
||||
if req, ok := m.requestsByTokenHash[hash]; ok {
|
||||
reqCopy := *req
|
||||
return &reqCopy, nil
|
||||
}
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
func (m *mockAccountDeletionRepository) DeleteByID(id uint) error {
|
||||
if m.deleteByIDErr != nil {
|
||||
return m.deleteByIDErr
|
||||
}
|
||||
|
||||
if req, ok := m.requests[id]; ok {
|
||||
delete(m.requests, id)
|
||||
delete(m.requestsByTokenHash, req.TokenHash)
|
||||
return nil
|
||||
}
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
func (m *mockAccountDeletionRepository) DeleteByUserID(userID uint) error {
|
||||
if m.deleteByUserIDErr != nil {
|
||||
return m.deleteByUserIDErr
|
||||
}
|
||||
|
||||
for id, req := range m.requests {
|
||||
if req.UserID == userID {
|
||||
delete(m.requests, id)
|
||||
delete(m.requestsByTokenHash, req.TokenHash)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNewAccountDeletionService(t *testing.T) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
postRepo := testutils.NewMockPostRepository()
|
||||
deletionRepo := newMockAccountDeletionRepository()
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
|
||||
|
||||
service := NewAccountDeletionService(userRepo, postRepo, deletionRepo, emailService)
|
||||
|
||||
if service == nil {
|
||||
t.Fatal("expected service to be created")
|
||||
}
|
||||
|
||||
if service.userRepo != userRepo {
|
||||
t.Error("expected userRepo to be set")
|
||||
}
|
||||
|
||||
if service.postRepo != postRepo {
|
||||
t.Error("expected postRepo to be set")
|
||||
}
|
||||
|
||||
if service.deletionRepo != deletionRepo {
|
||||
t.Error("expected deletionRepo to be set")
|
||||
}
|
||||
|
||||
if service.emailService != emailService {
|
||||
t.Error("expected emailService to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountDeletionService_GetUserIDFromDeletionToken(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
setupRepo func() *mockAccountDeletionRepository
|
||||
expectedID uint
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "successful retrieval",
|
||||
token: "valid-token",
|
||||
setupRepo: func() *mockAccountDeletionRepository {
|
||||
repo := newMockAccountDeletionRepository()
|
||||
req := &database.AccountDeletionRequest{
|
||||
UserID: 1,
|
||||
TokenHash: HashVerificationToken("valid-token"),
|
||||
ExpiresAt: time.Now().Add(time.Hour),
|
||||
}
|
||||
repo.Create(req)
|
||||
return repo
|
||||
},
|
||||
expectedID: 1,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "empty token",
|
||||
token: "",
|
||||
setupRepo: func() *mockAccountDeletionRepository { return newMockAccountDeletionRepository() },
|
||||
expectedID: 0,
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
{
|
||||
name: "whitespace only token",
|
||||
token: " ",
|
||||
setupRepo: func() *mockAccountDeletionRepository { return newMockAccountDeletionRepository() },
|
||||
expectedID: 0,
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
{
|
||||
name: "token not found",
|
||||
token: "invalid-token",
|
||||
setupRepo: func() *mockAccountDeletionRepository {
|
||||
return newMockAccountDeletionRepository()
|
||||
},
|
||||
expectedID: 0,
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
{
|
||||
name: "expired token",
|
||||
token: "expired-token",
|
||||
setupRepo: func() *mockAccountDeletionRepository {
|
||||
repo := newMockAccountDeletionRepository()
|
||||
req := &database.AccountDeletionRequest{
|
||||
UserID: 1,
|
||||
TokenHash: HashVerificationToken("expired-token"),
|
||||
ExpiresAt: time.Now().Add(-time.Hour),
|
||||
}
|
||||
repo.Create(req)
|
||||
return repo
|
||||
},
|
||||
expectedID: 0,
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
token: "valid-token",
|
||||
setupRepo: func() *mockAccountDeletionRepository {
|
||||
repo := newMockAccountDeletionRepository()
|
||||
repo.getByTokenHashErr = errors.New("database error")
|
||||
return repo
|
||||
},
|
||||
expectedID: 0,
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
postRepo := testutils.NewMockPostRepository()
|
||||
deletionRepo := tt.setupRepo()
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, &testutils.MockEmailSender{})
|
||||
|
||||
service := NewAccountDeletionService(userRepo, postRepo, deletionRepo, emailService)
|
||||
|
||||
userID, err := service.GetUserIDFromDeletionToken(tt.token)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
if !errors.Is(err, tt.expectedError) {
|
||||
t.Errorf("expected error %v, got %v", tt.expectedError, err)
|
||||
}
|
||||
} else {
|
||||
if tt.name == "repository error" || tt.name == "nil repository" {
|
||||
if err == nil {
|
||||
t.Error("expected error but got none")
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if userID != tt.expectedID {
|
||||
t.Errorf("expected userID %d, got %d", tt.expectedID, userID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountDeletionService_RequestAccountDeletion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userID uint
|
||||
setupMocks func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender)
|
||||
expectedError bool
|
||||
checkToken bool
|
||||
}{
|
||||
{
|
||||
name: "successful request",
|
||||
userID: 1,
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{ID: 1, Username: "testuser", Email: "test@example.com"}
|
||||
userRepo.Create(user)
|
||||
|
||||
deletionRepo := newMockAccountDeletionRepository()
|
||||
emailSender := &testutils.MockEmailSender{}
|
||||
|
||||
return userRepo, deletionRepo, emailSender
|
||||
},
|
||||
expectedError: false,
|
||||
checkToken: true,
|
||||
},
|
||||
{
|
||||
name: "invalid user ID",
|
||||
userID: 0,
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), newMockAccountDeletionRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
checkToken: false,
|
||||
},
|
||||
{
|
||||
name: "user not found",
|
||||
userID: 999,
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), newMockAccountDeletionRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: true,
|
||||
checkToken: false,
|
||||
},
|
||||
{
|
||||
name: "email service error",
|
||||
userID: 1,
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{ID: 1, Username: "testuser", Email: "test@example.com"}
|
||||
userRepo.Create(user)
|
||||
|
||||
deletionRepo := newMockAccountDeletionRepository()
|
||||
var errorSender errorEmailSender
|
||||
errorSender.err = errors.New("email service error")
|
||||
emailSender := &errorSender
|
||||
|
||||
return userRepo, deletionRepo, emailSender
|
||||
},
|
||||
expectedError: true,
|
||||
checkToken: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
userRepo, deletionRepo, emailSender := tt.setupMocks()
|
||||
postRepo := testutils.NewMockPostRepository()
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
|
||||
|
||||
service := NewAccountDeletionService(userRepo, postRepo, deletionRepo, emailService)
|
||||
|
||||
err := service.RequestAccountDeletion(tt.userID)
|
||||
|
||||
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.checkToken {
|
||||
if len(deletionRepo.requests) == 0 {
|
||||
t.Error("expected deletion request to be created")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountDeletionService_ConfirmAccountDeletion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
setupMocks func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender)
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "successful deletion",
|
||||
token: "valid-token",
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{ID: 1, Username: "testuser", Email: "test@example.com"}
|
||||
userRepo.Create(user)
|
||||
|
||||
deletionRepo := newMockAccountDeletionRepository()
|
||||
req := &database.AccountDeletionRequest{
|
||||
UserID: 1,
|
||||
TokenHash: HashVerificationToken("valid-token"),
|
||||
ExpiresAt: time.Now().Add(time.Hour),
|
||||
}
|
||||
deletionRepo.Create(req)
|
||||
|
||||
emailSender := &testutils.MockEmailSender{}
|
||||
|
||||
return userRepo, deletionRepo, emailSender
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "empty token",
|
||||
token: "",
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), newMockAccountDeletionRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
{
|
||||
name: "token not found",
|
||||
token: "invalid-token",
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), newMockAccountDeletionRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
{
|
||||
name: "expired token",
|
||||
token: "expired-token",
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
deletionRepo := newMockAccountDeletionRepository()
|
||||
req := &database.AccountDeletionRequest{
|
||||
UserID: 1,
|
||||
TokenHash: HashVerificationToken("expired-token"),
|
||||
ExpiresAt: time.Now().Add(-time.Hour),
|
||||
}
|
||||
deletionRepo.Create(req)
|
||||
|
||||
return testutils.NewMockUserRepository(), deletionRepo, &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
{
|
||||
name: "user not found",
|
||||
token: "valid-token",
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
deletionRepo := newMockAccountDeletionRepository()
|
||||
req := &database.AccountDeletionRequest{
|
||||
UserID: 999,
|
||||
TokenHash: HashVerificationToken("valid-token"),
|
||||
ExpiresAt: time.Now().Add(time.Hour),
|
||||
}
|
||||
deletionRepo.Create(req)
|
||||
|
||||
return testutils.NewMockUserRepository(), deletionRepo, &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
userRepo, deletionRepo, emailSender := tt.setupMocks()
|
||||
postRepo := testutils.NewMockPostRepository()
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
|
||||
|
||||
service := NewAccountDeletionService(userRepo, postRepo, deletionRepo, emailService)
|
||||
|
||||
err := service.ConfirmAccountDeletion(tt.token)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
if !errors.Is(err, tt.expectedError) {
|
||||
t.Errorf("expected error %v, got %v", tt.expectedError, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountDeletionService_ConfirmAccountDeletionWithPosts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
deletePosts bool
|
||||
setupMocks func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender)
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "successful deletion without posts",
|
||||
token: "valid-token",
|
||||
deletePosts: false,
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{ID: 1, Username: "testuser", Email: "test@example.com"}
|
||||
userRepo.Create(user)
|
||||
|
||||
deletionRepo := newMockAccountDeletionRepository()
|
||||
req := &database.AccountDeletionRequest{
|
||||
UserID: 1,
|
||||
TokenHash: HashVerificationToken("valid-token"),
|
||||
ExpiresAt: time.Now().Add(time.Hour),
|
||||
}
|
||||
deletionRepo.Create(req)
|
||||
|
||||
emailSender := &testutils.MockEmailSender{}
|
||||
|
||||
return userRepo, deletionRepo, emailSender
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "successful deletion with posts",
|
||||
token: "valid-token",
|
||||
deletePosts: true,
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
userRepo := testutils.NewMockUserRepository()
|
||||
user := &database.User{ID: 1, Username: "testuser", Email: "test@example.com"}
|
||||
userRepo.Create(user)
|
||||
|
||||
deletionRepo := newMockAccountDeletionRepository()
|
||||
req := &database.AccountDeletionRequest{
|
||||
UserID: 1,
|
||||
TokenHash: HashVerificationToken("valid-token"),
|
||||
ExpiresAt: time.Now().Add(time.Hour),
|
||||
}
|
||||
deletionRepo.Create(req)
|
||||
|
||||
emailSender := &testutils.MockEmailSender{}
|
||||
|
||||
return userRepo, deletionRepo, emailSender
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "empty token",
|
||||
token: "",
|
||||
deletePosts: false,
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
return testutils.NewMockUserRepository(), newMockAccountDeletionRepository(), &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
{
|
||||
name: "expired token",
|
||||
token: "expired-token",
|
||||
deletePosts: false,
|
||||
setupMocks: func() (*testutils.MockUserRepository, *mockAccountDeletionRepository, EmailSender) {
|
||||
deletionRepo := newMockAccountDeletionRepository()
|
||||
req := &database.AccountDeletionRequest{
|
||||
UserID: 1,
|
||||
TokenHash: HashVerificationToken("expired-token"),
|
||||
ExpiresAt: time.Now().Add(-time.Hour),
|
||||
}
|
||||
deletionRepo.Create(req)
|
||||
|
||||
return testutils.NewMockUserRepository(), deletionRepo, &testutils.MockEmailSender{}
|
||||
},
|
||||
expectedError: ErrInvalidDeletionToken,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
userRepo, deletionRepo, emailSender := tt.setupMocks()
|
||||
postRepo := testutils.NewMockPostRepository()
|
||||
emailService, _ := NewEmailService(testutils.AppTestConfig, emailSender)
|
||||
|
||||
service := NewAccountDeletionService(userRepo, postRepo, deletionRepo, emailService)
|
||||
|
||||
err := service.ConfirmAccountDeletionWithPosts(tt.token, tt.deletePosts)
|
||||
|
||||
if tt.expectedError != nil {
|
||||
if !errors.Is(err, tt.expectedError) {
|
||||
t.Errorf("expected error %v, got %v", tt.expectedError, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountDeletionService_UserHasPosts(t *testing.T) {
|
||||
}
|
||||
Reference in New Issue
Block a user