1018 lines
22 KiB
Go
1018 lines
22 KiB
Go
package testutils
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"goyco/internal/database"
|
|
"goyco/internal/repositories"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type MockEmailSender struct {
|
|
sendFunc func(to, subject, body string) error
|
|
lastVerificationToken string
|
|
lastDeletionToken string
|
|
lastPasswordResetToken string
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func (m *MockEmailSender) Send(to, subject, body string) error {
|
|
if m.sendFunc != nil {
|
|
return m.sendFunc(to, subject, body)
|
|
}
|
|
|
|
if len(body) == 0 {
|
|
return nil
|
|
}
|
|
|
|
normalized := strings.ToLower(strings.TrimSpace(subject))
|
|
|
|
token := extractTokenFromBody(body)
|
|
|
|
switch {
|
|
case strings.Contains(normalized, "resend") && strings.Contains(normalized, "confirm"):
|
|
m.SetVerificationToken(defaultIfEmpty(token, "test-verification-token"))
|
|
case strings.Contains(normalized, "confirm your goyco account") || strings.Contains(normalized, "confirm your account"):
|
|
m.SetVerificationToken(defaultIfEmpty(token, "test-verification-token"))
|
|
case strings.Contains(normalized, "confirm") && strings.Contains(normalized, "email"):
|
|
m.SetVerificationToken(defaultIfEmpty(token, "test-verification-token"))
|
|
case strings.Contains(normalized, "confirm your new email"):
|
|
m.SetVerificationToken(defaultIfEmpty(token, "test-verification-token"))
|
|
case strings.Contains(normalized, "account deletion"):
|
|
m.SetDeletionToken(defaultIfEmpty(token, "test-deletion-token"))
|
|
case strings.Contains(normalized, "password reset") || strings.Contains(normalized, "reset your") || strings.Contains(normalized, "reset password"):
|
|
m.SetPasswordResetToken(defaultIfEmpty(token, "test-password-reset-token"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *MockEmailSender) GetLastVerificationToken() string {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.lastVerificationToken
|
|
}
|
|
|
|
func (m *MockEmailSender) GetLastDeletionToken() string {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.lastDeletionToken
|
|
}
|
|
|
|
func (m *MockEmailSender) GetLastPasswordResetToken() string {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.lastPasswordResetToken
|
|
}
|
|
|
|
func (m *MockEmailSender) Reset() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.lastVerificationToken = ""
|
|
m.lastDeletionToken = ""
|
|
m.lastPasswordResetToken = ""
|
|
}
|
|
|
|
func (m *MockEmailSender) VerificationToken() string {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.lastVerificationToken
|
|
}
|
|
|
|
func (m *MockEmailSender) SetVerificationToken(token string) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.lastVerificationToken = token
|
|
}
|
|
|
|
func (m *MockEmailSender) DeletionToken() string {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.lastDeletionToken
|
|
}
|
|
|
|
func (m *MockEmailSender) PasswordResetToken() string {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.lastPasswordResetToken
|
|
}
|
|
|
|
func (m *MockEmailSender) SetDeletionToken(token string) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.lastDeletionToken = token
|
|
}
|
|
|
|
func (m *MockEmailSender) SetPasswordResetToken(token string) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.lastPasswordResetToken = token
|
|
}
|
|
|
|
func defaultIfEmpty(value, fallback string) string {
|
|
if strings.TrimSpace(value) == "" {
|
|
return fallback
|
|
}
|
|
return value
|
|
}
|
|
|
|
func extractTokenFromBody(body string) string {
|
|
index := strings.Index(body, "token=")
|
|
if index == -1 {
|
|
return ""
|
|
}
|
|
|
|
tokenPart := body[index+len("token="):]
|
|
|
|
if delimIdx := strings.IndexAny(tokenPart, "&\"'\\\r\n <>"); delimIdx != -1 {
|
|
tokenPart = tokenPart[:delimIdx]
|
|
}
|
|
|
|
trimmed := strings.Trim(tokenPart, "\"' ")
|
|
if trimmed == "" {
|
|
return ""
|
|
}
|
|
|
|
unescaped, err := url.QueryUnescape(trimmed)
|
|
if err != nil {
|
|
return trimmed
|
|
}
|
|
|
|
return unescaped
|
|
}
|
|
|
|
type MockTitleFetcher struct {
|
|
fetchFunc func(ctx context.Context, url string) (string, error)
|
|
title string
|
|
err error
|
|
}
|
|
|
|
func (m *MockTitleFetcher) FetchTitle(ctx context.Context, url string) (string, error) {
|
|
if m.fetchFunc != nil {
|
|
return m.fetchFunc(ctx, url)
|
|
}
|
|
if m.err != nil {
|
|
return "", m.err
|
|
}
|
|
return m.title, nil
|
|
}
|
|
|
|
func (m *MockTitleFetcher) SetTitle(title string) {
|
|
m.title = title
|
|
m.err = nil
|
|
}
|
|
|
|
func (m *MockTitleFetcher) SetError(err error) {
|
|
m.err = err
|
|
m.title = ""
|
|
}
|
|
|
|
type MockUserRepository struct {
|
|
users map[uint]*database.User
|
|
usersByUsername map[string]*database.User
|
|
usersByEmail map[string]*database.User
|
|
usersByVerificationToken map[string]*database.User
|
|
usersByPasswordResetToken map[string]*database.User
|
|
deletedUsers map[uint]*database.User
|
|
nextID uint
|
|
createErr error
|
|
getByIDErr error
|
|
getByUsernameErr error
|
|
getByEmailErr error
|
|
getByVerificationTokenErr error
|
|
getByPasswordResetTokenErr error
|
|
updateErr error
|
|
deleteErr error
|
|
mu sync.RWMutex
|
|
|
|
GetAllFunc func(limit, offset int) ([]database.User, error)
|
|
GetDeletedUsersFunc func() ([]database.User, error)
|
|
HardDeleteAllFunc func() (int64, error)
|
|
|
|
GetErr error
|
|
DeleteErr error
|
|
|
|
Users map[uint]*database.User
|
|
DeletedUsers map[uint]*database.User
|
|
}
|
|
|
|
func NewMockUserRepository() *MockUserRepository {
|
|
return &MockUserRepository{
|
|
users: make(map[uint]*database.User),
|
|
usersByUsername: make(map[string]*database.User),
|
|
usersByEmail: make(map[string]*database.User),
|
|
usersByVerificationToken: make(map[string]*database.User),
|
|
usersByPasswordResetToken: make(map[string]*database.User),
|
|
deletedUsers: make(map[uint]*database.User),
|
|
nextID: 1,
|
|
Users: make(map[uint]*database.User),
|
|
DeletedUsers: make(map[uint]*database.User),
|
|
}
|
|
}
|
|
|
|
func (m *MockUserRepository) Create(user *database.User) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.createErr != nil {
|
|
return m.createErr
|
|
}
|
|
|
|
user.ID = m.nextID
|
|
m.nextID++
|
|
|
|
now := time.Now()
|
|
user.CreatedAt = now
|
|
user.UpdatedAt = now
|
|
|
|
userCopy := *user
|
|
m.users[user.ID] = &userCopy
|
|
m.usersByUsername[user.Username] = &userCopy
|
|
m.usersByEmail[user.Email] = &userCopy
|
|
m.Users[user.ID] = &userCopy
|
|
|
|
if user.EmailVerificationToken != "" {
|
|
m.usersByVerificationToken[user.EmailVerificationToken] = &userCopy
|
|
}
|
|
if user.PasswordResetToken != "" {
|
|
m.usersByPasswordResetToken[user.PasswordResetToken] = &userCopy
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *MockUserRepository) GetByID(id uint) (*database.User, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if m.getByIDErr != nil {
|
|
return nil, m.getByIDErr
|
|
}
|
|
|
|
if user, ok := m.users[id]; ok {
|
|
userCopy := *user
|
|
return &userCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockUserRepository) GetByUsername(username string) (*database.User, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if m.getByUsernameErr != nil {
|
|
return nil, m.getByUsernameErr
|
|
}
|
|
|
|
if user, ok := m.usersByUsername[username]; ok {
|
|
userCopy := *user
|
|
return &userCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockUserRepository) GetByUsernameIncludingDeleted(username string) (*database.User, error) {
|
|
return m.GetByUsername(username)
|
|
}
|
|
|
|
func (m *MockUserRepository) GetByIDIncludingDeleted(id uint) (*database.User, error) {
|
|
return m.GetByID(id)
|
|
}
|
|
|
|
func (m *MockUserRepository) GetByEmail(email string) (*database.User, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if m.getByEmailErr != nil {
|
|
return nil, m.getByEmailErr
|
|
}
|
|
|
|
if user, ok := m.usersByEmail[email]; ok {
|
|
userCopy := *user
|
|
return &userCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockUserRepository) GetByVerificationToken(token string) (*database.User, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if m.getByVerificationTokenErr != nil {
|
|
return nil, m.getByVerificationTokenErr
|
|
}
|
|
|
|
if user, ok := m.usersByVerificationToken[token]; ok {
|
|
userCopy := *user
|
|
return &userCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockUserRepository) GetByPasswordResetToken(token string) (*database.User, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if m.getByPasswordResetTokenErr != nil {
|
|
return nil, m.getByPasswordResetTokenErr
|
|
}
|
|
|
|
if user, ok := m.usersByPasswordResetToken[token]; ok {
|
|
userCopy := *user
|
|
return &userCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockUserRepository) GetAll(limit, offset int) ([]database.User, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
if m.GetAllFunc != nil {
|
|
return m.GetAllFunc(limit, offset)
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var users []database.User
|
|
count := 0
|
|
for _, user := range m.users {
|
|
if count >= offset && count < offset+limit {
|
|
users = append(users, *user)
|
|
}
|
|
count++
|
|
}
|
|
return users, nil
|
|
}
|
|
|
|
func (m *MockUserRepository) Update(user *database.User) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.updateErr != nil {
|
|
return m.updateErr
|
|
}
|
|
|
|
if _, ok := m.users[user.ID]; !ok {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
user.UpdatedAt = time.Now()
|
|
|
|
userCopy := *user
|
|
m.users[user.ID] = &userCopy
|
|
m.usersByUsername[user.Username] = &userCopy
|
|
m.usersByEmail[user.Email] = &userCopy
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *MockUserRepository) Delete(id uint) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.DeleteErr != nil {
|
|
return m.DeleteErr
|
|
}
|
|
|
|
if user, ok := m.users[id]; ok {
|
|
delete(m.users, id)
|
|
delete(m.usersByUsername, user.Username)
|
|
delete(m.usersByEmail, user.Email)
|
|
return nil
|
|
}
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockUserRepository) HardDelete(id uint) error {
|
|
return m.Delete(id)
|
|
}
|
|
|
|
func (m *MockUserRepository) SoftDeleteWithPosts(id uint) error {
|
|
return m.Delete(id)
|
|
}
|
|
|
|
func (m *MockUserRepository) GetPosts(userID uint, limit, offset int) ([]database.Post, error) {
|
|
return []database.Post{}, nil
|
|
}
|
|
|
|
func (m *MockUserRepository) Lock(id uint) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *MockUserRepository) Unlock(id uint) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *MockUserRepository) GetDeletedUsers() ([]database.User, error) {
|
|
if m.GetDeletedUsersFunc != nil {
|
|
return m.GetDeletedUsersFunc()
|
|
}
|
|
return []database.User{}, nil
|
|
}
|
|
|
|
func (m *MockUserRepository) HardDeleteAll() (int64, error) {
|
|
if m.HardDeleteAllFunc != nil {
|
|
return m.HardDeleteAllFunc()
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
count := int64(len(m.users))
|
|
m.users = make(map[uint]*database.User)
|
|
m.usersByUsername = make(map[string]*database.User)
|
|
m.usersByEmail = make(map[string]*database.User)
|
|
m.usersByVerificationToken = make(map[string]*database.User)
|
|
m.usersByPasswordResetToken = make(map[string]*database.User)
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (m *MockUserRepository) Count() (int64, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return int64(len(m.users)), nil
|
|
}
|
|
|
|
func (m *MockUserRepository) WithTx(tx *gorm.DB) repositories.UserRepository {
|
|
return m
|
|
}
|
|
|
|
func (m *MockUserRepository) SetCreateError(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.createErr = err
|
|
}
|
|
|
|
func (m *MockUserRepository) SetGetByIDError(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.getByIDErr = err
|
|
}
|
|
|
|
func (m *MockUserRepository) SetGetByUsernameError(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.getByUsernameErr = err
|
|
}
|
|
|
|
func (m *MockUserRepository) SetGetByEmailError(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.getByEmailErr = err
|
|
}
|
|
|
|
func (m *MockUserRepository) SetUpdateError(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.updateErr = err
|
|
}
|
|
|
|
func (m *MockUserRepository) SetDeleteError(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.deleteErr = err
|
|
}
|
|
|
|
type MockPostRepository struct {
|
|
createFunc func(*database.Post) error
|
|
getByIDFunc func(uint) (*database.Post, error)
|
|
getAllFunc func(int, int) ([]database.Post, error)
|
|
getByUserIDFunc func(uint, int, int) ([]database.Post, error)
|
|
updateFunc func(*database.Post) error
|
|
deleteFunc func(uint) error
|
|
countFunc func() (int64, error)
|
|
countByUserIDFunc func(uint) (int64, error)
|
|
getTopPostsFunc func(int) ([]database.Post, error)
|
|
getNewestPostsFunc func(int) ([]database.Post, error)
|
|
searchFunc func(string, int, int) ([]database.Post, error)
|
|
getPostsByDeletedUsersFunc func() ([]database.Post, error)
|
|
hardDeletePostsByDeletedUsersFunc func() (int64, error)
|
|
hardDeleteAllFunc func() (int64, error)
|
|
withTxFunc func(*gorm.DB) repositories.PostRepository
|
|
|
|
GetPostsByDeletedUsersFunc func() ([]database.Post, error)
|
|
HardDeletePostsByDeletedUsersFunc func() (int64, error)
|
|
HardDeleteAllFunc func() (int64, error)
|
|
CountFunc func() (int64, error)
|
|
|
|
posts map[uint]*database.Post
|
|
nextID uint
|
|
mu sync.RWMutex
|
|
|
|
SearchCalls []SearchCall
|
|
|
|
GetErr error
|
|
DeleteErr error
|
|
SearchErr error
|
|
|
|
Posts map[uint]*database.Post
|
|
}
|
|
|
|
type SearchCall struct {
|
|
Query string
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
func NewMockPostRepository() *MockPostRepository {
|
|
return &MockPostRepository{
|
|
posts: make(map[uint]*database.Post),
|
|
nextID: 1,
|
|
Posts: make(map[uint]*database.Post),
|
|
}
|
|
}
|
|
|
|
func (m *MockPostRepository) Create(post *database.Post) error {
|
|
if m.createFunc != nil {
|
|
return m.createFunc(post)
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
post.ID = m.nextID
|
|
m.nextID++
|
|
|
|
postCopy := *post
|
|
m.posts[post.ID] = &postCopy
|
|
m.Posts[post.ID] = &postCopy
|
|
return nil
|
|
}
|
|
|
|
func (m *MockPostRepository) GetByID(id uint) (*database.Post, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
if m.getByIDFunc != nil {
|
|
return m.getByIDFunc(id)
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if post, ok := m.posts[id]; ok {
|
|
postCopy := *post
|
|
return &postCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockPostRepository) GetAll(limit, offset int) ([]database.Post, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
if m.getAllFunc != nil {
|
|
return m.getAllFunc(limit, offset)
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var posts []database.Post
|
|
count := 0
|
|
for _, post := range m.posts {
|
|
if count >= offset && count < offset+limit {
|
|
posts = append(posts, *post)
|
|
}
|
|
count++
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (m *MockPostRepository) GetByUserID(userID uint, limit, offset int) ([]database.Post, error) {
|
|
if m.GetErr != nil {
|
|
return nil, m.GetErr
|
|
}
|
|
if m.getByUserIDFunc != nil {
|
|
return m.getByUserIDFunc(userID, limit, offset)
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var posts []database.Post
|
|
count := 0
|
|
for _, post := range m.posts {
|
|
if post.AuthorID != nil && *post.AuthorID == userID {
|
|
if count >= offset && count < offset+limit {
|
|
posts = append(posts, *post)
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (m *MockPostRepository) Update(post *database.Post) error {
|
|
if m.updateFunc != nil {
|
|
return m.updateFunc(post)
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if _, ok := m.posts[post.ID]; !ok {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
postCopy := *post
|
|
m.posts[post.ID] = &postCopy
|
|
m.Posts[post.ID] = &postCopy
|
|
return nil
|
|
}
|
|
|
|
func (m *MockPostRepository) Delete(id uint) error {
|
|
if m.DeleteErr != nil {
|
|
return m.DeleteErr
|
|
}
|
|
if m.deleteFunc != nil {
|
|
return m.deleteFunc(id)
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if _, ok := m.posts[id]; !ok {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
delete(m.posts, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *MockPostRepository) Count() (int64, error) {
|
|
if m.CountFunc != nil {
|
|
return m.CountFunc()
|
|
}
|
|
if m.countFunc != nil {
|
|
return m.countFunc()
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return int64(len(m.posts)), nil
|
|
}
|
|
|
|
func (m *MockPostRepository) CountByUserID(userID uint) (int64, error) {
|
|
if m.countByUserIDFunc != nil {
|
|
return m.countByUserIDFunc(userID)
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
count := int64(0)
|
|
for _, post := range m.posts {
|
|
if post.AuthorID != nil && *post.AuthorID == userID {
|
|
count++
|
|
}
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
func (m *MockPostRepository) GetTopPosts(limit int) ([]database.Post, error) {
|
|
if m.getTopPostsFunc != nil {
|
|
return m.getTopPostsFunc(limit)
|
|
}
|
|
return m.GetAll(limit, 0)
|
|
}
|
|
|
|
func (m *MockPostRepository) GetNewestPosts(limit int) ([]database.Post, error) {
|
|
if m.getNewestPostsFunc != nil {
|
|
return m.getNewestPostsFunc(limit)
|
|
}
|
|
return m.GetAll(limit, 0)
|
|
}
|
|
|
|
func (m *MockPostRepository) Search(query string, limit, offset int) ([]database.Post, error) {
|
|
if m.SearchErr != nil {
|
|
return nil, m.SearchErr
|
|
}
|
|
|
|
m.mu.Lock()
|
|
m.SearchCalls = append(m.SearchCalls, SearchCall{
|
|
Query: query,
|
|
Limit: limit,
|
|
Offset: offset,
|
|
})
|
|
m.mu.Unlock()
|
|
|
|
if m.searchFunc != nil {
|
|
return m.searchFunc(query, limit, offset)
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var posts []database.Post
|
|
count := 0
|
|
for _, post := range m.posts {
|
|
if containsIgnoreCase(post.Title, query) || containsIgnoreCase(post.Content, query) {
|
|
if count >= offset && count < offset+limit {
|
|
posts = append(posts, *post)
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (m *MockPostRepository) WithTx(tx *gorm.DB) repositories.PostRepository {
|
|
if m.withTxFunc != nil {
|
|
return m.withTxFunc(tx)
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (m *MockPostRepository) GetPostsByDeletedUsers() ([]database.Post, error) {
|
|
if m.GetPostsByDeletedUsersFunc != nil {
|
|
return m.GetPostsByDeletedUsersFunc()
|
|
}
|
|
if m.getPostsByDeletedUsersFunc != nil {
|
|
return m.getPostsByDeletedUsersFunc()
|
|
}
|
|
return []database.Post{}, nil
|
|
}
|
|
|
|
func (m *MockPostRepository) HardDeletePostsByDeletedUsers() (int64, error) {
|
|
if m.HardDeletePostsByDeletedUsersFunc != nil {
|
|
return m.HardDeletePostsByDeletedUsersFunc()
|
|
}
|
|
if m.hardDeletePostsByDeletedUsersFunc != nil {
|
|
return m.hardDeletePostsByDeletedUsersFunc()
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
func (m *MockPostRepository) HardDeleteAll() (int64, error) {
|
|
if m.HardDeleteAllFunc != nil {
|
|
return m.HardDeleteAllFunc()
|
|
}
|
|
if m.hardDeleteAllFunc != nil {
|
|
return m.hardDeleteAllFunc()
|
|
}
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
count := int64(len(m.posts))
|
|
m.posts = make(map[uint]*database.Post)
|
|
return count, nil
|
|
}
|
|
|
|
func containsIgnoreCase(s, substr string) bool {
|
|
return len(s) >= len(substr)
|
|
}
|
|
|
|
type MockVoteRepository struct {
|
|
votes map[uint]*database.Vote
|
|
byUserPost map[string]*database.Vote
|
|
nextID uint
|
|
createErr error
|
|
updateErr error
|
|
deleteErr error
|
|
mu sync.RWMutex
|
|
|
|
DeleteErr error
|
|
}
|
|
|
|
func NewMockVoteRepository() *MockVoteRepository {
|
|
return &MockVoteRepository{
|
|
votes: make(map[uint]*database.Vote),
|
|
byUserPost: make(map[string]*database.Vote),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
func (m *MockVoteRepository) Create(vote *database.Vote) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.createErr != nil {
|
|
return m.createErr
|
|
}
|
|
|
|
var key string
|
|
if vote.UserID != nil {
|
|
key = m.key(*vote.UserID, vote.PostID)
|
|
} else {
|
|
key = fmt.Sprintf("anon-%d", vote.PostID)
|
|
}
|
|
if existingVote, exists := m.byUserPost[key]; exists {
|
|
existingVote.Type = vote.Type
|
|
existingVote.UpdatedAt = vote.UpdatedAt
|
|
vote.ID = existingVote.ID
|
|
return nil
|
|
}
|
|
|
|
vote.ID = m.nextID
|
|
m.nextID++
|
|
|
|
voteCopy := *vote
|
|
m.votes[vote.ID] = &voteCopy
|
|
m.byUserPost[key] = &voteCopy
|
|
return nil
|
|
}
|
|
|
|
func (m *MockVoteRepository) CreateOrUpdate(vote *database.Vote) error {
|
|
return m.Create(vote)
|
|
}
|
|
|
|
func (m *MockVoteRepository) GetByID(id uint) (*database.Vote, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if vote, ok := m.votes[id]; ok {
|
|
voteCopy := *vote
|
|
return &voteCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockVoteRepository) GetByUserAndPost(userID, postID uint) (*database.Vote, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
key := m.key(userID, postID)
|
|
if vote, ok := m.byUserPost[key]; ok {
|
|
voteCopy := *vote
|
|
return &voteCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockVoteRepository) GetByVoteHash(voteHash string) (*database.Vote, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
for _, vote := range m.votes {
|
|
if vote.VoteHash != nil && *vote.VoteHash == voteHash {
|
|
voteCopy := *vote
|
|
return &voteCopy, nil
|
|
}
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockVoteRepository) GetByPostID(postID uint) ([]database.Vote, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var votes []database.Vote
|
|
for _, vote := range m.votes {
|
|
if vote.PostID == postID {
|
|
votes = append(votes, *vote)
|
|
}
|
|
}
|
|
return votes, nil
|
|
}
|
|
|
|
func (m *MockVoteRepository) GetByUserID(userID uint) ([]database.Vote, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var votes []database.Vote
|
|
for _, vote := range m.votes {
|
|
if vote.UserID != nil && *vote.UserID == userID {
|
|
votes = append(votes, *vote)
|
|
}
|
|
}
|
|
return votes, nil
|
|
}
|
|
|
|
func (m *MockVoteRepository) Update(vote *database.Vote) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.updateErr != nil {
|
|
return m.updateErr
|
|
}
|
|
|
|
if _, ok := m.votes[vote.ID]; !ok {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
voteCopy := *vote
|
|
m.votes[vote.ID] = &voteCopy
|
|
var key string
|
|
if vote.UserID != nil {
|
|
key = m.key(*vote.UserID, vote.PostID)
|
|
} else {
|
|
key = fmt.Sprintf("anon-%d", vote.PostID)
|
|
}
|
|
m.byUserPost[key] = &voteCopy
|
|
return nil
|
|
}
|
|
|
|
func (m *MockVoteRepository) Delete(id uint) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.DeleteErr != nil {
|
|
return m.DeleteErr
|
|
}
|
|
|
|
if vote, ok := m.votes[id]; ok {
|
|
delete(m.votes, id)
|
|
var key string
|
|
if vote.UserID != nil {
|
|
key = m.key(*vote.UserID, vote.PostID)
|
|
} else {
|
|
key = fmt.Sprintf("anon-%d", vote.PostID)
|
|
}
|
|
delete(m.byUserPost, key)
|
|
return nil
|
|
}
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *MockVoteRepository) CountByPostID(postID uint) (int64, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
count := int64(0)
|
|
for _, vote := range m.votes {
|
|
if vote.PostID == postID {
|
|
count++
|
|
}
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
func (m *MockVoteRepository) CountByUserID(userID uint) (int64, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
count := int64(0)
|
|
for _, vote := range m.votes {
|
|
if vote.UserID != nil && *vote.UserID == userID {
|
|
count++
|
|
}
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
func (m *MockVoteRepository) GetVoteCountsByPostID(postID uint) (int, int, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
upVotes := 0
|
|
downVotes := 0
|
|
for _, vote := range m.votes {
|
|
if vote.PostID == postID {
|
|
switch vote.Type {
|
|
case database.VoteUp:
|
|
upVotes++
|
|
case database.VoteDown:
|
|
downVotes++
|
|
}
|
|
}
|
|
}
|
|
return upVotes, downVotes, nil
|
|
}
|
|
|
|
func (m *MockVoteRepository) Count() (int64, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return int64(len(m.votes)), nil
|
|
}
|
|
|
|
func (m *MockVoteRepository) WithTx(tx *gorm.DB) repositories.VoteRepository {
|
|
return m
|
|
}
|
|
|
|
func (m *MockVoteRepository) key(userID, postID uint) string {
|
|
return fmt.Sprintf("%d-%d", userID, postID)
|
|
}
|
|
|
|
func (m *MockVoteRepository) SetCreateError(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.createErr = err
|
|
}
|
|
|
|
func (m *MockVoteRepository) SetUpdateError(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.updateErr = err
|
|
}
|
|
|
|
func (m *MockVoteRepository) SetDeleteError(err error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.deleteErr = err
|
|
}
|