Files
goyco/internal/testutils/mocks.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
}