939 lines
19 KiB
Go
939 lines
19 KiB
Go
package services
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"goyco/internal/database"
|
|
"goyco/internal/repositories"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type mockVoteRepo struct {
|
|
votes map[uint]*database.Vote
|
|
byUserPost map[string]*database.Vote
|
|
byVoteHash map[string]*database.Vote
|
|
nextID uint
|
|
createErr error
|
|
getByUserAndPostErr error
|
|
getByVoteHashErr error
|
|
getByPostIDErr error
|
|
updateErr error
|
|
deleteErr error
|
|
createCalls int
|
|
updateCalls int
|
|
deleteCalls int
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func newMockVoteRepo() *mockVoteRepo {
|
|
return &mockVoteRepo{
|
|
votes: make(map[uint]*database.Vote),
|
|
byUserPost: make(map[string]*database.Vote),
|
|
byVoteHash: make(map[string]*database.Vote),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
func (m *mockVoteRepo) 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 if vote.VoteHash != nil {
|
|
key = *vote.VoteHash
|
|
} else {
|
|
return errors.New("vote must have either user_id or vote_hash")
|
|
}
|
|
|
|
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
|
|
if vote.VoteHash != nil {
|
|
m.byVoteHash[*vote.VoteHash] = &voteCopy
|
|
}
|
|
|
|
m.createCalls++
|
|
return nil
|
|
}
|
|
|
|
func (m *mockVoteRepo) CreateOrUpdate(vote *database.Vote) error {
|
|
return m.Create(vote)
|
|
}
|
|
|
|
func (m *mockVoteRepo) 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 *mockVoteRepo) GetByUserAndPost(userID, postID uint) (*database.Vote, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if m.getByUserAndPostErr != nil {
|
|
return nil, m.getByUserAndPostErr
|
|
}
|
|
|
|
key := m.key(userID, postID)
|
|
if vote, ok := m.byUserPost[key]; ok {
|
|
voteCopy := *vote
|
|
return &voteCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *mockVoteRepo) GetByVoteHash(voteHash string) (*database.Vote, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if m.getByVoteHashErr != nil {
|
|
return nil, m.getByVoteHashErr
|
|
}
|
|
|
|
if vote, ok := m.byVoteHash[voteHash]; ok {
|
|
voteCopy := *vote
|
|
return &voteCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *mockVoteRepo) GetByPostID(postID uint) ([]database.Vote, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if m.getByPostIDErr != nil {
|
|
return nil, m.getByPostIDErr
|
|
}
|
|
|
|
var votes []database.Vote
|
|
for _, vote := range m.votes {
|
|
if vote.PostID == postID {
|
|
votes = append(votes, *vote)
|
|
}
|
|
}
|
|
return votes, nil
|
|
}
|
|
|
|
func (m *mockVoteRepo) 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 *mockVoteRepo) 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)
|
|
m.byUserPost[key] = &voteCopy
|
|
}
|
|
if vote.VoteHash != nil {
|
|
m.byVoteHash[*vote.VoteHash] = &voteCopy
|
|
}
|
|
|
|
m.updateCalls++
|
|
return nil
|
|
}
|
|
|
|
func (m *mockVoteRepo) Delete(id uint) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.deleteErr != nil {
|
|
return m.deleteErr
|
|
}
|
|
|
|
vote, ok := m.votes[id]
|
|
if !ok {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
delete(m.votes, id)
|
|
|
|
var key string
|
|
if vote.UserID != nil {
|
|
key = m.key(*vote.UserID, vote.PostID)
|
|
delete(m.byUserPost, key)
|
|
}
|
|
if vote.VoteHash != nil {
|
|
delete(m.byVoteHash, *vote.VoteHash)
|
|
}
|
|
|
|
m.deleteCalls++
|
|
return nil
|
|
}
|
|
|
|
func (m *mockVoteRepo) Count() (int64, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return int64(len(m.votes)), nil
|
|
}
|
|
|
|
func (m *mockVoteRepo) 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 *mockVoteRepo) 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 *mockVoteRepo) 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 *mockVoteRepo) WithTx(tx *gorm.DB) repositories.VoteRepository {
|
|
return m
|
|
}
|
|
|
|
func (m *mockVoteRepo) key(userID, postID uint) string {
|
|
return fmt.Sprintf("%d:%d", userID, postID)
|
|
}
|
|
|
|
type mockPostRepo struct {
|
|
posts map[uint]*database.Post
|
|
nextID uint
|
|
getErr error
|
|
updateErr error
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func newMockPostRepo() *mockPostRepo {
|
|
return &mockPostRepo{
|
|
posts: make(map[uint]*database.Post),
|
|
nextID: 1,
|
|
}
|
|
}
|
|
|
|
func (m *mockPostRepo) GetByID(id uint) (*database.Post, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
if m.getErr != nil {
|
|
return nil, m.getErr
|
|
}
|
|
|
|
if post, ok := m.posts[id]; ok {
|
|
postCopy := *post
|
|
return &postCopy, nil
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *mockPostRepo) Update(post *database.Post) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.updateErr != nil {
|
|
return m.updateErr
|
|
}
|
|
|
|
if _, ok := m.posts[post.ID]; !ok {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
postCopy := *post
|
|
m.posts[post.ID] = &postCopy
|
|
return nil
|
|
}
|
|
|
|
func (m *mockPostRepo) Create(post *database.Post) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
post.ID = m.nextID
|
|
m.nextID++
|
|
|
|
postCopy := *post
|
|
m.posts[post.ID] = &postCopy
|
|
return nil
|
|
}
|
|
|
|
func (m *mockPostRepo) GetByURL(url string) (*database.Post, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
for _, post := range m.posts {
|
|
if post.URL == url {
|
|
postCopy := *post
|
|
return &postCopy, nil
|
|
}
|
|
}
|
|
return nil, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
func (m *mockPostRepo) GetByAuthorID(authorID uint) ([]database.Post, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var posts []database.Post
|
|
for _, post := range m.posts {
|
|
if post.AuthorID != nil && *post.AuthorID == authorID {
|
|
posts = append(posts, *post)
|
|
}
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (m *mockPostRepo) GetAll(limit, offset int) ([]database.Post, error) {
|
|
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 *mockPostRepo) Count() (int64, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return int64(len(m.posts)), nil
|
|
}
|
|
|
|
func (m *mockPostRepo) Delete(id uint) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if _, ok := m.posts[id]; !ok {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
|
|
delete(m.posts, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *mockPostRepo) GetByUserID(userID uint, limit, offset int) ([]database.Post, error) {
|
|
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 *mockPostRepo) CountByUserID(userID uint) (int64, error) {
|
|
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 *mockPostRepo) GetTopPosts(limit int) ([]database.Post, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var posts []database.Post
|
|
count := 0
|
|
for _, post := range m.posts {
|
|
if count < limit {
|
|
posts = append(posts, *post)
|
|
count++
|
|
}
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (m *mockPostRepo) GetNewestPosts(limit int) ([]database.Post, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var posts []database.Post
|
|
count := 0
|
|
for _, post := range m.posts {
|
|
if count < limit {
|
|
posts = append(posts, *post)
|
|
count++
|
|
}
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (m *mockPostRepo) Search(query string, limit, offset int) ([]database.Post, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var posts []database.Post
|
|
count := 0
|
|
for _, post := range m.posts {
|
|
if strings.Contains(strings.ToLower(post.Title), strings.ToLower(query)) {
|
|
if count >= offset && count < offset+limit {
|
|
posts = append(posts, *post)
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (m *mockPostRepo) GetPostsByDeletedUsers() ([]database.Post, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var posts []database.Post
|
|
for _, post := range m.posts {
|
|
if post.AuthorID == nil {
|
|
posts = append(posts, *post)
|
|
}
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (m *mockPostRepo) HardDeletePostsByDeletedUsers() (int64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
count := int64(0)
|
|
for id, post := range m.posts {
|
|
if post.AuthorID == nil {
|
|
delete(m.posts, id)
|
|
count++
|
|
}
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
func (m *mockPostRepo) HardDeleteAll() (int64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
count := int64(len(m.posts))
|
|
m.posts = make(map[uint]*database.Post)
|
|
return count, nil
|
|
}
|
|
|
|
func (m *mockPostRepo) WithTx(tx *gorm.DB) repositories.PostRepository {
|
|
return m
|
|
}
|
|
|
|
func TestVoteService_CastVote_Authenticated(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
post := &database.Post{
|
|
ID: 1,
|
|
Title: "Test Post",
|
|
URL: "https://example.com",
|
|
AuthorID: &[]uint{1}[0],
|
|
UpVotes: 0,
|
|
DownVotes: 0,
|
|
Score: 0,
|
|
}
|
|
postRepo.posts[1] = post
|
|
|
|
req := VoteRequest{
|
|
UserID: 1,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
IPAddress: "127.0.0.1",
|
|
UserAgent: "test-agent",
|
|
}
|
|
|
|
result, err := service.CastVote(req)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if result == nil {
|
|
t.Fatal("Expected result, got nil")
|
|
}
|
|
|
|
if result.Type != database.VoteUp {
|
|
t.Errorf("Expected vote type 'up', got '%v'", result.Type)
|
|
}
|
|
|
|
if result.UpVotes != 1 {
|
|
t.Errorf("Expected up votes to be 1, got %d", result.UpVotes)
|
|
}
|
|
|
|
if result.DownVotes != 0 {
|
|
t.Errorf("Expected down votes to be 0, got %d", result.DownVotes)
|
|
}
|
|
|
|
if result.Score != 1 {
|
|
t.Errorf("Expected score to be 1, got %d", result.Score)
|
|
}
|
|
|
|
if result.IsUnauthenticated {
|
|
t.Error("Expected IsUnauthenticated to be false for authenticated vote")
|
|
}
|
|
}
|
|
|
|
func TestVoteService_CastVote_Unauthenticated(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
post := &database.Post{
|
|
ID: 1,
|
|
Title: "Test Post",
|
|
URL: "https://example.com",
|
|
AuthorID: &[]uint{1}[0],
|
|
UpVotes: 0,
|
|
DownVotes: 0,
|
|
Score: 0,
|
|
}
|
|
postRepo.posts[1] = post
|
|
|
|
req := VoteRequest{
|
|
UserID: 0,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
IPAddress: "127.0.0.1",
|
|
UserAgent: "test-agent",
|
|
}
|
|
|
|
result, err := service.CastVote(req)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if result == nil {
|
|
t.Fatal("Expected result, got nil")
|
|
}
|
|
|
|
if result.Type != database.VoteUp {
|
|
t.Errorf("Expected vote type 'up', got '%v'", result.Type)
|
|
}
|
|
|
|
if result.UpVotes != 1 {
|
|
t.Errorf("Expected up votes to be 1, got %d", result.UpVotes)
|
|
}
|
|
|
|
if result.DownVotes != 0 {
|
|
t.Errorf("Expected down votes to be 0, got %d", result.DownVotes)
|
|
}
|
|
|
|
if result.Score != 1 {
|
|
t.Errorf("Expected score to be 1, got %d", result.Score)
|
|
}
|
|
|
|
if !result.IsUnauthenticated {
|
|
t.Error("Expected IsUnauthenticated to be true for unauthenticated vote")
|
|
}
|
|
}
|
|
|
|
func TestVoteService_CastVote_UpdateExisting(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
post := &database.Post{
|
|
ID: 1,
|
|
Title: "Test Post",
|
|
URL: "https://example.com",
|
|
AuthorID: &[]uint{1}[0],
|
|
UpVotes: 0,
|
|
DownVotes: 0,
|
|
Score: 0,
|
|
}
|
|
postRepo.posts[1] = post
|
|
|
|
req := VoteRequest{
|
|
UserID: 1,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
IPAddress: "127.0.0.1",
|
|
UserAgent: "test-agent",
|
|
}
|
|
|
|
_, err := service.CastVote(req)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
req.Type = database.VoteDown
|
|
result, err := service.CastVote(req)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if result.UpVotes != 0 {
|
|
t.Errorf("Expected up votes to be 0, got %d", result.UpVotes)
|
|
}
|
|
|
|
if result.DownVotes != 1 {
|
|
t.Errorf("Expected down votes to be 1, got %d", result.DownVotes)
|
|
}
|
|
|
|
if result.Score != -1 {
|
|
t.Errorf("Expected score to be -1, got %d", result.Score)
|
|
}
|
|
}
|
|
|
|
func TestVoteService_CastVote_RemoveVote(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
post := &database.Post{
|
|
ID: 1,
|
|
Title: "Test Post",
|
|
URL: "https://example.com",
|
|
AuthorID: &[]uint{1}[0],
|
|
UpVotes: 0,
|
|
DownVotes: 0,
|
|
Score: 0,
|
|
}
|
|
postRepo.posts[1] = post
|
|
|
|
req := VoteRequest{
|
|
UserID: 1,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
IPAddress: "127.0.0.1",
|
|
UserAgent: "test-agent",
|
|
}
|
|
|
|
_, err := service.CastVote(req)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
req.Type = database.VoteNone
|
|
result, err := service.CastVote(req)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if result.UpVotes != 0 {
|
|
t.Errorf("Expected up votes to be 0, got %d", result.UpVotes)
|
|
}
|
|
|
|
if result.DownVotes != 0 {
|
|
t.Errorf("Expected down votes to be 0, got %d", result.DownVotes)
|
|
}
|
|
|
|
if result.Score != 0 {
|
|
t.Errorf("Expected score to be 0, got %d", result.Score)
|
|
}
|
|
}
|
|
|
|
func TestVoteService_GetUserVote_Authenticated(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
userID := uint(1)
|
|
vote := &database.Vote{
|
|
ID: 1,
|
|
UserID: &userID,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
}
|
|
voteRepo.votes[1] = vote
|
|
voteRepo.byUserPost["1:1"] = vote
|
|
|
|
result, err := service.GetUserVote(1, 1, "127.0.0.1", "test-agent")
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if result == nil {
|
|
t.Fatal("Expected vote, got nil")
|
|
}
|
|
|
|
if result.Type != database.VoteUp {
|
|
t.Errorf("Expected vote type 'up', got '%v'", result.Type)
|
|
}
|
|
|
|
if result.UserID == nil || *result.UserID != 1 {
|
|
t.Errorf("Expected user ID 1, got %v", result.UserID)
|
|
}
|
|
}
|
|
|
|
func TestVoteService_GetUserVote_Unauthenticated(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
voteHash := service.GenerateVoteHash("127.0.0.1", "test-agent", 1)
|
|
vote := &database.Vote{
|
|
ID: 1,
|
|
UserID: nil,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
VoteHash: &voteHash,
|
|
}
|
|
voteRepo.votes[1] = vote
|
|
voteRepo.byVoteHash[voteHash] = vote
|
|
|
|
result, err := service.GetUserVote(0, 1, "127.0.0.1", "test-agent")
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if result == nil {
|
|
t.Fatal("Expected vote, got nil")
|
|
}
|
|
|
|
if result.Type != database.VoteUp {
|
|
t.Errorf("Expected vote type 'up', got '%v'", result.Type)
|
|
}
|
|
|
|
if result.UserID != nil {
|
|
t.Error("Expected UserID to be nil for unauthenticated vote")
|
|
}
|
|
|
|
if result.VoteHash == nil || *result.VoteHash != voteHash {
|
|
t.Errorf("Expected vote hash '%s', got %v", voteHash, result.VoteHash)
|
|
}
|
|
}
|
|
|
|
func TestVoteService_GetPostVotes(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
userID1 := uint(1)
|
|
userID2 := uint(2)
|
|
voteHash := "test-hash"
|
|
|
|
vote1 := &database.Vote{
|
|
ID: 1,
|
|
UserID: &userID1,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
}
|
|
vote2 := &database.Vote{
|
|
ID: 2,
|
|
UserID: &userID2,
|
|
PostID: 1,
|
|
Type: database.VoteDown,
|
|
}
|
|
vote3 := &database.Vote{
|
|
ID: 3,
|
|
UserID: nil,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
VoteHash: &voteHash,
|
|
}
|
|
|
|
voteRepo.votes[1] = vote1
|
|
voteRepo.votes[2] = vote2
|
|
voteRepo.votes[3] = vote3
|
|
|
|
votes, err := service.GetPostVotes(1)
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if len(votes) != 3 {
|
|
t.Errorf("Expected 3 votes, got %d", len(votes))
|
|
}
|
|
|
|
hasAuthenticated := false
|
|
hasUnauthenticated := false
|
|
for _, vote := range votes {
|
|
if vote.UserID != nil {
|
|
hasAuthenticated = true
|
|
}
|
|
if vote.VoteHash != nil {
|
|
hasUnauthenticated = true
|
|
}
|
|
}
|
|
|
|
if !hasAuthenticated {
|
|
t.Error("Expected to find authenticated votes")
|
|
}
|
|
|
|
if !hasUnauthenticated {
|
|
t.Error("Expected to find unauthenticated votes")
|
|
}
|
|
}
|
|
|
|
func TestVoteService_GetVoteStatistics(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
userID1 := uint(1)
|
|
userID2 := uint(2)
|
|
voteHash := "test-hash"
|
|
|
|
vote1 := &database.Vote{
|
|
ID: 1,
|
|
UserID: &userID1,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
}
|
|
vote2 := &database.Vote{
|
|
ID: 2,
|
|
UserID: &userID2,
|
|
PostID: 1,
|
|
Type: database.VoteDown,
|
|
}
|
|
vote3 := &database.Vote{
|
|
ID: 3,
|
|
UserID: nil,
|
|
PostID: 1,
|
|
Type: database.VoteUp,
|
|
VoteHash: &voteHash,
|
|
}
|
|
|
|
voteRepo.votes[1] = vote1
|
|
voteRepo.votes[2] = vote2
|
|
voteRepo.votes[3] = vote3
|
|
|
|
authenticatedCount, anonymousCount, err := service.GetVoteStatistics()
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
|
|
if authenticatedCount != 3 {
|
|
t.Errorf("Expected total count to be 3, got %d", authenticatedCount)
|
|
}
|
|
|
|
if anonymousCount != 0 {
|
|
t.Errorf("Expected unauthenticated count to be 0, got %d", anonymousCount)
|
|
}
|
|
}
|
|
|
|
func TestVoteService_Validation(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
req := VoteRequest{
|
|
UserID: 1,
|
|
PostID: 0,
|
|
Type: database.VoteUp,
|
|
IPAddress: "127.0.0.1",
|
|
UserAgent: "test-agent",
|
|
}
|
|
|
|
_, err := service.CastVote(req)
|
|
if err == nil {
|
|
t.Error("Expected error for missing post ID")
|
|
}
|
|
|
|
req.PostID = 1
|
|
req.Type = "invalid"
|
|
|
|
_, err = service.CastVote(req)
|
|
if err == nil {
|
|
t.Error("Expected error for invalid vote type")
|
|
}
|
|
}
|
|
|
|
func TestVoteService_PostNotFound(t *testing.T) {
|
|
voteRepo := newMockVoteRepo()
|
|
postRepo := newMockPostRepo()
|
|
service := NewVoteService(voteRepo, postRepo, nil)
|
|
|
|
req := VoteRequest{
|
|
UserID: 1,
|
|
PostID: 999,
|
|
Type: database.VoteUp,
|
|
IPAddress: "127.0.0.1",
|
|
UserAgent: "test-agent",
|
|
}
|
|
|
|
_, err := service.CastVote(req)
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent post")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "post not found") {
|
|
t.Errorf("Expected 'post not found' error, got %v", err)
|
|
}
|
|
}
|