Files
goyco/internal/repositories/user_repository_test.go

1260 lines
30 KiB
Go

package repositories
import (
"errors"
"fmt"
"strconv"
"strings"
"testing"
"time"
"gorm.io/gorm"
"goyco/internal/database"
)
func TestUserRepository_Create(t *testing.T) {
suite := NewTestSuite(t)
t.Run("successful creation", func(t *testing.T) {
suite.Reset()
user := &database.User{
Username: "testuser",
Email: "test@example.com",
Password: "password123",
EmailVerified: true,
}
err := suite.UserRepo.Create(user)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if suite.GetUserCount() != 1 {
t.Errorf("Expected 1 user, got %d", suite.GetUserCount())
}
if user.ID == 0 {
t.Error("Expected user ID to be assigned")
}
})
t.Run("duplicate username", func(t *testing.T) {
suite.Reset()
user1 := &database.User{
Username: "duplicate",
Email: "user1@example.com",
Password: "password123",
EmailVerified: true,
}
err := suite.UserRepo.Create(user1)
if err != nil {
t.Fatalf("Expected no error for first user, got %v", err)
}
user2 := &database.User{
Username: "duplicate",
Email: "user2@example.com",
Password: "password123",
EmailVerified: true,
}
err = suite.UserRepo.Create(user2)
if err == nil {
t.Error("Expected error for duplicate username")
}
if !errors.Is(err, gorm.ErrDuplicatedKey) && !strings.Contains(err.Error(), "duplicate") && !strings.Contains(err.Error(), "UNIQUE constraint") {
t.Errorf("Expected duplicate key error, got %v", err)
}
})
t.Run("duplicate email", func(t *testing.T) {
suite.Reset()
user1 := &database.User{
Username: "user1",
Email: "duplicate@example.com",
Password: "password123",
EmailVerified: true,
}
err := suite.UserRepo.Create(user1)
if err != nil {
t.Fatalf("Expected no error for first user, got %v", err)
}
user2 := &database.User{
Username: "user2",
Email: "duplicate@example.com",
Password: "password123",
EmailVerified: true,
}
err = suite.UserRepo.Create(user2)
if err == nil {
t.Error("Expected error for duplicate email")
}
if !errors.Is(err, gorm.ErrDuplicatedKey) && !strings.Contains(err.Error(), "duplicate") && !strings.Contains(err.Error(), "UNIQUE constraint") {
t.Errorf("Expected duplicate key error, got %v", err)
}
})
}
func TestUserRepository_GetByID(t *testing.T) {
suite := NewTestSuite(t)
t.Run("existing user", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
retrieved, err := suite.UserRepo.GetByID(user.ID)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected user, got nil")
}
if retrieved.ID != user.ID {
t.Errorf("Expected ID %d, got %d", user.ID, retrieved.ID)
}
if retrieved.Username != user.Username {
t.Errorf("Expected username %s, got %s", user.Username, retrieved.Username)
}
if retrieved.Email != user.Email {
t.Errorf("Expected email %s, got %s", user.Email, retrieved.Email)
}
})
t.Run("non-existing user", func(t *testing.T) {
suite.Reset()
_, err := suite.UserRepo.GetByID(999)
if err == nil {
t.Error("Expected error for non-existing user")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
})
}
func TestUserRepository_GetByUsername(t *testing.T) {
suite := NewTestSuite(t)
t.Run("existing user", func(t *testing.T) {
suite.Reset()
suite.CreateTestUser("testuser", "test@example.com", "password123")
retrieved, err := suite.UserRepo.GetByUsername("testuser")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected user, got nil")
}
if retrieved.Username != "testuser" {
t.Errorf("Expected username 'testuser', got %s", retrieved.Username)
}
})
t.Run("non-existing user", func(t *testing.T) {
suite.Reset()
_, err := suite.UserRepo.GetByUsername("nonexistent")
if err == nil {
t.Error("Expected error for non-existing username")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
})
}
func TestUserRepository_GetByUsernameIncludingDeleted(t *testing.T) {
suite := NewTestSuite(t)
t.Run("existing user", func(t *testing.T) {
suite.Reset()
suite.CreateTestUser("testuser", "test@example.com", "password123")
retrieved, err := suite.UserRepo.GetByUsernameIncludingDeleted("testuser")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected user, got nil")
}
if retrieved.Username != "testuser" {
t.Errorf("Expected username 'testuser', got %s", retrieved.Username)
}
})
t.Run("deleted user", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("deleteduser", "deleted@example.com", "password123")
err := suite.UserRepo.Delete(user.ID)
if err != nil {
t.Fatalf("Failed to delete user: %v", err)
}
retrieved, err := suite.UserRepo.GetByUsernameIncludingDeleted("deleteduser")
if err != nil {
t.Fatalf("Expected no error for deleted user, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected deleted user, got nil")
}
if retrieved.Username != "deleteduser" {
t.Errorf("Expected username 'deleteduser', got %s", retrieved.Username)
}
})
}
func TestUserRepository_GetByEmail(t *testing.T) {
suite := NewTestSuite(t)
t.Run("existing user", func(t *testing.T) {
suite.Reset()
suite.CreateTestUser("testuser", "test@example.com", "password123")
retrieved, err := suite.UserRepo.GetByEmail("test@example.com")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected user, got nil")
}
if retrieved.Email != "test@example.com" {
t.Errorf("Expected email 'test@example.com', got %s", retrieved.Email)
}
})
t.Run("non-existing email", func(t *testing.T) {
suite.Reset()
_, err := suite.UserRepo.GetByEmail("nonexistent@example.com")
if err == nil {
t.Error("Expected error for non-existing email")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
})
t.Run("email normalization to lowercase", func(t *testing.T) {
suite.Reset()
user := &database.User{
Username: "testuser",
Email: "Test@Example.COM",
Password: "password123",
EmailVerified: true,
}
err := suite.UserRepo.Create(user)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if user.Email != "test@example.com" {
t.Errorf("Expected email to be normalized to lowercase, got %s", user.Email)
}
retrieved, err := suite.UserRepo.GetByEmail("test@example.com")
if err != nil {
t.Fatalf("Expected to find user with normalized email, got error: %v", err)
}
if retrieved.Email != "test@example.com" {
t.Errorf("Expected normalized email, got %s", retrieved.Email)
}
})
}
func TestUserRepository_GetByVerificationToken(t *testing.T) {
suite := NewTestSuite(t)
t.Run("existing user with token", func(t *testing.T) {
suite.Reset()
user := &database.User{
Username: "testuser",
Email: "test@example.com",
Password: "password123",
EmailVerified: false,
}
user.EmailVerificationToken = "verification-token-123"
err := suite.UserRepo.Create(user)
if err != nil {
t.Fatalf("Failed to create user: %v", err)
}
retrieved, err := suite.UserRepo.GetByVerificationToken("verification-token-123")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected user, got nil")
}
if retrieved.EmailVerificationToken != "verification-token-123" {
t.Errorf("Expected token 'verification-token-123', got %s", retrieved.EmailVerificationToken)
}
})
t.Run("non-existing token", func(t *testing.T) {
suite.Reset()
_, err := suite.UserRepo.GetByVerificationToken("nonexistent-token")
if err == nil {
t.Error("Expected error for non-existing token")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
})
}
func TestUserRepository_GetByPasswordResetToken(t *testing.T) {
suite := NewTestSuite(t)
t.Run("existing user with token", func(t *testing.T) {
suite.Reset()
user := &database.User{
Username: "testuser",
Email: "test@example.com",
Password: "password123",
EmailVerified: true,
PasswordResetToken: "reset-token-123",
}
err := suite.UserRepo.Create(user)
if err != nil {
t.Fatalf("Failed to create user: %v", err)
}
retrieved, err := suite.UserRepo.GetByPasswordResetToken("reset-token-123")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected user, got nil")
}
if retrieved.PasswordResetToken != "reset-token-123" {
t.Errorf("Expected token 'reset-token-123', got %s", retrieved.PasswordResetToken)
}
})
t.Run("non-existing token", func(t *testing.T) {
suite.Reset()
_, err := suite.UserRepo.GetByPasswordResetToken("nonexistent-token")
if err == nil {
t.Error("Expected error for non-existing token")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
})
}
func TestUserRepository_GetAll(t *testing.T) {
suite := NewTestSuite(t)
t.Run("empty database", func(t *testing.T) {
suite.Reset()
users, err := suite.UserRepo.GetAll(10, 0)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(users) != 0 {
t.Errorf("Expected 0 users, got %d", len(users))
}
})
t.Run("with users", func(t *testing.T) {
suite.Reset()
suite.CreateTestUser("user1", "user1@example.com", "password123")
suite.CreateTestUser("user2", "user2@example.com", "password123")
suite.CreateTestUser("user3", "user3@example.com", "password123")
retrieved, err := suite.UserRepo.GetAll(10, 0)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(retrieved) != 3 {
t.Errorf("Expected 3 users, got %d", len(retrieved))
}
})
t.Run("with limit", func(t *testing.T) {
suite.Reset()
suite.CreateTestUser("user4", "user4@example.com", "password123")
suite.CreateTestUser("user5", "user5@example.com", "password123")
suite.CreateTestUser("user6", "user6@example.com", "password123")
retrieved, err := suite.UserRepo.GetAll(2, 0)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(retrieved) != 2 {
t.Errorf("Expected 2 users, got %d", len(retrieved))
}
})
t.Run("with offset", func(t *testing.T) {
suite.Reset()
suite.CreateTestUser("user7", "user7@example.com", "password123")
suite.CreateTestUser("user8", "user8@example.com", "password123")
suite.CreateTestUser("user9", "user9@example.com", "password123")
retrieved, err := suite.UserRepo.GetAll(2, 1)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(retrieved) != 2 {
t.Errorf("Expected 2 users, got %d", len(retrieved))
}
})
}
func TestUserRepository_Update(t *testing.T) {
suite := NewTestSuite(t)
t.Run("successful update", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
user.Username = "updateduser"
user.Email = "updated@example.com"
err := suite.UserRepo.Update(user)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
retrieved, err := suite.UserRepo.GetByID(user.ID)
if err != nil {
t.Fatalf("Failed to retrieve user: %v", err)
}
if retrieved.Username != "updateduser" {
t.Errorf("Expected username 'updateduser', got %s", retrieved.Username)
}
if retrieved.Email != "updated@example.com" {
t.Errorf("Expected email 'updated@example.com', got %s", retrieved.Email)
}
})
t.Run("update non-existing user", func(t *testing.T) {
suite.Reset()
initialCount, err := suite.UserRepo.Count()
if err != nil {
t.Fatalf("Failed to get initial count: %v", err)
}
user := &database.User{
ID: 999,
Username: "nonexistent",
Email: "nonexistent@example.com",
Password: "password123",
EmailVerified: true,
}
err = suite.UserRepo.Update(user)
if err != nil {
t.Fatalf("Update should succeed even for non-existing user (GORM behavior)")
}
finalCount, err := suite.UserRepo.Count()
if err != nil {
t.Fatalf("Failed to get final count: %v", err)
}
if finalCount != initialCount {
t.Errorf("Expected count to remain %d, got %d (update should not create new record)", initialCount, finalCount)
}
_, err = suite.UserRepo.GetByID(999)
if err == nil {
t.Error("Expected error for non-existing user after update")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
})
}
func TestUserRepository_Delete(t *testing.T) {
suite := NewTestSuite(t)
t.Run("successful soft delete", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
err := suite.UserRepo.Delete(user.ID)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
_, err = suite.UserRepo.GetByID(user.ID)
if err == nil {
t.Error("Expected error for soft deleted user")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
retrieved, err := suite.UserRepo.GetByUsernameIncludingDeleted("testuser")
if err != nil {
t.Fatalf("Expected no error for deleted user with including deleted method, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected deleted user to be found with including deleted method")
}
})
t.Run("delete non-existing user", func(t *testing.T) {
suite.Reset()
err := suite.UserRepo.Delete(999)
if err != nil {
t.Fatalf("Delete should succeed even for non-existing user (GORM behavior)")
}
})
}
func TestUserRepository_HardDelete(t *testing.T) {
suite := NewTestSuite(t)
t.Run("successful hard delete", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
err := suite.UserRepo.HardDelete(user.ID)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
_, err = suite.UserRepo.GetByID(user.ID)
if err == nil {
t.Error("Expected error for hard deleted user")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
_, err = suite.UserRepo.GetByUsernameIncludingDeleted("testuser")
if err == nil {
t.Error("Expected error for hard deleted user with including deleted method")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
})
}
func TestUserRepository_GetPosts(t *testing.T) {
suite := NewTestSuite(t)
t.Run("user with posts", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
suite.CreateTestPost(user.ID, "Post 1", "https://example.com/1", "Content 1")
suite.CreateTestPost(user.ID, "Post 2", "https://example.com/2", "Content 2")
retrieved, err := suite.UserRepo.GetPosts(user.ID, 10, 0)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(retrieved) != 2 {
t.Errorf("Expected 2 posts, got %d", len(retrieved))
}
})
t.Run("user without posts", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("noposts", "noposts@example.com", "password123")
retrieved, err := suite.UserRepo.GetPosts(user.ID, 10, 0)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(retrieved) != 0 {
t.Errorf("Expected 0 posts, got %d", len(retrieved))
}
})
t.Run("with limit and offset", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("pagination", "pagination@example.com", "password123")
for i := 0; i < 5; i++ {
suite.CreateTestPost(user.ID,
"Post "+strconv.Itoa(i),
"https://example.com/"+strconv.Itoa(i),
"Content "+strconv.Itoa(i))
}
retrieved, err := suite.UserRepo.GetPosts(user.ID, 2, 0)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(retrieved) != 2 {
t.Errorf("Expected 2 posts, got %d", len(retrieved))
}
retrieved, err = suite.UserRepo.GetPosts(user.ID, 2, 1)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(retrieved) != 2 {
t.Errorf("Expected 2 posts with offset, got %d", len(retrieved))
}
})
}
func TestUserRepository_EdgeCases(t *testing.T) {
suite := NewTestSuite(t)
t.Run("empty username", func(t *testing.T) {
suite.Reset()
user := &database.User{
Username: "",
Email: "test@example.com",
Password: "password123",
EmailVerified: true,
}
err := suite.UserRepo.Create(user)
if err == nil {
t.Error("Expected validation error for empty username")
}
})
t.Run("empty email", func(t *testing.T) {
suite.Reset()
user := &database.User{
Username: "testuser",
Email: "",
Password: "password123",
EmailVerified: true,
}
err := suite.UserRepo.Create(user)
if err == nil {
t.Error("Expected validation error for empty email")
}
})
t.Run("invalid email format", func(t *testing.T) {
suite.Reset()
user := &database.User{
Username: "testuser",
Email: "invalid-email",
Password: "password123",
EmailVerified: true,
}
err := suite.UserRepo.Create(user)
if err == nil {
t.Error("Expected validation error for invalid email format")
}
})
t.Run("very long username", func(t *testing.T) {
suite.Reset()
longUsername := strings.Repeat("a", 300)
user := &database.User{
Username: longUsername,
Email: "test@example.com",
Password: "password123",
EmailVerified: true,
}
err := suite.UserRepo.Create(user)
if err != nil {
t.Errorf("Unexpected error for long username: %v", err)
}
})
t.Run("nil pointer handling", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
post := suite.CreateTestPost(user.ID, "Test Post", "https://example.com", "Test content")
if post.AuthorID == nil {
t.Error("Expected AuthorID to be set")
}
})
t.Run("maximum length email", func(t *testing.T) {
suite.Reset()
longEmail := strings.Repeat("a", 200) + "@example.com"
if len(longEmail) > 255 {
longEmail = strings.Repeat("a", 200) + "@ex.com"
}
user := &database.User{
Username: "testuser",
Email: longEmail,
Password: "password123",
EmailVerified: true,
}
err := suite.UserRepo.Create(user)
if err != nil {
t.Errorf("Unexpected error for long email: %v", err)
}
})
}
func TestUserRepository_GetByIDIncludingDeleted(t *testing.T) {
suite := NewTestSuite(t)
t.Run("existing user", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
retrieved, err := suite.UserRepo.GetByIDIncludingDeleted(user.ID)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected user, got nil")
}
if retrieved.ID != user.ID {
t.Errorf("Expected ID %d, got %d", user.ID, retrieved.ID)
}
})
t.Run("deleted user", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
err := suite.UserRepo.Delete(user.ID)
if err != nil {
t.Fatalf("Failed to delete user: %v", err)
}
retrieved, err := suite.UserRepo.GetByIDIncludingDeleted(user.ID)
if err != nil {
t.Fatalf("Expected no error for deleted user, got %v", err)
}
if retrieved == nil {
t.Fatal("Expected deleted user, got nil")
}
if retrieved.ID != user.ID {
t.Errorf("Expected ID %d, got %d", user.ID, retrieved.ID)
}
})
t.Run("non-existing user", func(t *testing.T) {
suite.Reset()
_, err := suite.UserRepo.GetByIDIncludingDeleted(999)
if err == nil {
t.Error("Expected error for non-existing user")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
})
}
func TestUserRepository_SoftDeleteWithPosts(t *testing.T) {
suite := NewTestSuite(t)
t.Run("successful soft delete with posts", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
post := suite.CreateTestPost(user.ID, "Test Post", "https://example.com", "Test content")
user2 := suite.CreateTestUser("voter", "voter@example.com", "password123")
suite.CreateTestVote(user2.ID, post.ID, database.VoteUp)
err := suite.UserRepo.SoftDeleteWithPosts(user.ID)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
_, err = suite.UserRepo.GetByID(user.ID)
if err == nil {
t.Error("Expected error for soft deleted user")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
retrievedPost, err := suite.PostRepo.GetByID(post.ID)
if err != nil {
t.Fatalf("Expected to find post, got %v", err)
}
if retrievedPost.AuthorID != nil {
t.Error("Expected post author to be null after user deletion")
}
})
t.Run("soft delete user without posts", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
err := suite.UserRepo.SoftDeleteWithPosts(user.ID)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
_, err = suite.UserRepo.GetByID(user.ID)
if err == nil {
t.Error("Expected error for soft deleted user")
}
if err != gorm.ErrRecordNotFound {
t.Errorf("Expected ErrRecordNotFound, got %v", err)
}
})
}
func TestUserRepository_Lock(t *testing.T) {
suite := NewTestSuite(t)
t.Run("successful lock", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
err := suite.UserRepo.Lock(user.ID)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
retrieved, err := suite.UserRepo.GetByID(user.ID)
if err != nil {
t.Fatalf("Failed to retrieve user: %v", err)
}
if !retrieved.Locked {
t.Error("Expected user to be locked")
}
})
t.Run("lock non-existing user", func(t *testing.T) {
suite.Reset()
err := suite.UserRepo.Lock(999)
if err != nil {
t.Fatalf("Lock should succeed even for non-existing user (GORM behavior)")
}
})
}
func TestUserRepository_Unlock(t *testing.T) {
suite := NewTestSuite(t)
t.Run("successful unlock", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("testuser", "test@example.com", "password123")
err := suite.UserRepo.Lock(user.ID)
if err != nil {
t.Fatalf("Failed to lock user: %v", err)
}
err = suite.UserRepo.Unlock(user.ID)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
retrieved, err := suite.UserRepo.GetByID(user.ID)
if err != nil {
t.Fatalf("Failed to retrieve user: %v", err)
}
if retrieved.Locked {
t.Error("Expected user to be unlocked")
}
})
t.Run("unlock non-existing user", func(t *testing.T) {
suite.Reset()
err := suite.UserRepo.Unlock(999)
if err != nil {
t.Fatalf("Unlock should succeed even for non-existing user (GORM behavior)")
}
})
}
func TestUserRepository_GetDeletedUsers(t *testing.T) {
suite := NewTestSuite(t)
t.Run("with deleted users", func(t *testing.T) {
suite.Reset()
user1 := suite.CreateTestUser("user1", "user1@example.com", "password123")
user2 := suite.CreateTestUser("user2", "user2@example.com", "password123")
user3 := suite.CreateTestUser("user3", "user3@example.com", "password123")
suite.UserRepo.Delete(user1.ID)
suite.UserRepo.Delete(user2.ID)
deletedUsers, err := suite.UserRepo.GetDeletedUsers()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(deletedUsers) != 2 {
t.Errorf("Expected 2 deleted users, got %d", len(deletedUsers))
}
deletedIDs := make(map[uint]bool)
for _, user := range deletedUsers {
deletedIDs[user.ID] = true
}
if !deletedIDs[user1.ID] {
t.Error("Expected user1 to be in deleted users list")
}
if !deletedIDs[user2.ID] {
t.Error("Expected user2 to be in deleted users list")
}
if deletedIDs[user3.ID] {
t.Error("Expected user3 to not be in deleted users list")
}
})
t.Run("no deleted users", func(t *testing.T) {
suite.Reset()
suite.CreateTestUser("user1", "user1@example.com", "password123")
deletedUsers, err := suite.UserRepo.GetDeletedUsers()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(deletedUsers) != 0 {
t.Errorf("Expected 0 deleted users, got %d", len(deletedUsers))
}
})
}
func TestUserRepository_HardDeleteAll(t *testing.T) {
suite := NewTestSuite(t)
t.Run("hard delete all users", func(t *testing.T) {
suite.Reset()
user1 := suite.CreateTestUser("user1", "user1@example.com", "password123")
user2 := suite.CreateTestUser("user2", "user2@example.com", "password123")
post := suite.CreateTestPost(user1.ID, "Test Post", "https://example.com", "Test content")
suite.CreateTestVote(user2.ID, post.ID, database.VoteUp)
deletedCount, err := suite.UserRepo.HardDeleteAll()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if deletedCount < 2 {
t.Errorf("Expected at least 2 records deleted, got %d", deletedCount)
}
users, err := suite.UserRepo.GetAll(10, 0)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(users) != 0 {
t.Errorf("Expected 0 users, got %d", len(users))
}
})
t.Run("hard delete with no users", func(t *testing.T) {
suite.Reset()
deletedCount, err := suite.UserRepo.HardDeleteAll()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if deletedCount != 0 {
t.Errorf("Expected 0 records deleted, got %d", deletedCount)
}
})
}
func TestUserRepository_Count(t *testing.T) {
suite := NewTestSuite(t)
t.Run("empty database", func(t *testing.T) {
suite.Reset()
count, err := suite.UserRepo.Count()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if count != 0 {
t.Errorf("Expected count 0, got %d", count)
}
})
t.Run("with users", func(t *testing.T) {
suite.Reset()
suite.CreateTestUser("user1", "user1@example.com", "password123")
suite.CreateTestUser("user2", "user2@example.com", "password123")
suite.CreateTestUser("user3", "user3@example.com", "password123")
count, err := suite.UserRepo.Count()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if count != 3 {
t.Errorf("Expected count 3, got %d", count)
}
})
t.Run("with deleted users", func(t *testing.T) {
suite.Reset()
user1 := suite.CreateTestUser("user1", "user1@example.com", "password123")
suite.CreateTestUser("user2", "user2@example.com", "password123")
suite.UserRepo.Delete(user1.ID)
count, err := suite.UserRepo.Count()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if count != 1 {
t.Errorf("Expected count 1, got %d", count)
}
})
}
func TestUserRepository_ConcurrentAccess(t *testing.T) {
suite := NewTestSuite(t)
t.Run("concurrent creates", func(t *testing.T) {
suite.Reset()
done := make(chan bool, 10)
errors := make(chan error, 10)
for i := 0; i < 10; i++ {
go func(id int) {
defer func() { done <- true }()
user := &database.User{
Username: fmt.Sprintf("concurrent%d", id),
Email: fmt.Sprintf("concurrent%d@example.com", id),
Password: "password123",
EmailVerified: true,
}
var err error
for retries := 0; retries < 5; retries++ {
err = suite.UserRepo.Create(user)
if err == nil {
break
}
if strings.Contains(err.Error(), "locked") {
time.Sleep(time.Millisecond * time.Duration(10*(retries+1)))
continue
}
break
}
if err != nil {
errors <- err
}
}(i)
}
for i := 0; i < 10; i++ {
<-done
}
close(errors)
for err := range errors {
if !strings.Contains(err.Error(), "locked") {
t.Errorf("Concurrent create failed: %v", err)
}
}
count, err := suite.UserRepo.Count()
if err != nil {
t.Fatalf("Failed to count users: %v", err)
}
if count < 7 {
t.Errorf("Expected at least 7 users (SQLite concurrency limitation), got %d", count)
}
})
t.Run("concurrent updates", func(t *testing.T) {
suite.Reset()
user := suite.CreateTestUser("concurrent_update", "update@example.com", "password123")
done := make(chan bool, 5)
for i := 0; i < 5; i++ {
go func(id int) {
defer func() { done <- true }()
user.Username = fmt.Sprintf("updated%d", id)
_ = suite.UserRepo.Update(user)
}(i)
}
for i := 0; i < 5; i++ {
<-done
}
retrieved, err := suite.UserRepo.GetByID(user.ID)
if err != nil {
t.Fatalf("Failed to retrieve user: %v", err)
}
if retrieved == nil {
t.Error("Expected user to exist after concurrent updates")
}
})
t.Run("concurrent deletes", func(t *testing.T) {
suite.Reset()
user1 := suite.CreateTestUser("delete1", "delete1@example.com", "password123")
user2 := suite.CreateTestUser("delete2", "delete2@example.com", "password123")
done := make(chan bool, 2)
go func() {
var err error
for retries := 0; retries < 5; retries++ {
err = suite.UserRepo.Delete(user1.ID)
if err == nil {
break
}
if strings.Contains(err.Error(), "locked") {
time.Sleep(time.Millisecond * time.Duration(10*(retries+1)))
continue
}
break
}
done <- true
}()
go func() {
var err error
for retries := 0; retries < 5; retries++ {
err = suite.UserRepo.Delete(user2.ID)
if err == nil {
break
}
if strings.Contains(err.Error(), "locked") {
time.Sleep(time.Millisecond * time.Duration(10*(retries+1)))
continue
}
break
}
done <- true
}()
<-done
<-done
count, err := suite.UserRepo.Count()
if err != nil {
t.Fatalf("Failed to count users: %v", err)
}
if count > 0 {
var err error
for retries := 0; retries < 5; retries++ {
count, err = suite.UserRepo.Count()
if err == nil && count == 0 {
break
}
time.Sleep(time.Millisecond * time.Duration(10*(retries+1)))
}
if count > 0 {
t.Errorf("Expected 0 users after concurrent deletes (SQLite concurrency limitation), got %d", count)
}
}
})
}
func TestUserRepository_WithTx(t *testing.T) {
suite := NewTestSuite(t)
t.Run("transaction repository", func(t *testing.T) {
suite.Reset()
tx := suite.DB.Begin()
defer tx.Rollback()
txUserRepo := suite.UserRepo.WithTx(tx)
user := &database.User{
Username: "txuser",
Email: "tx@example.com",
Password: "password123",
EmailVerified: true,
}
err := txUserRepo.Create(user)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
retrieved, err := txUserRepo.GetByID(user.ID)
if err != nil {
t.Fatalf("Expected to find user in transaction, got %v", err)
}
if retrieved.Username != "txuser" {
t.Errorf("Expected username 'txuser', got %s", retrieved.Username)
}
})
}