Files
user-system/internal/service/user_service_test.go

469 lines
12 KiB
Go

package service_test
import (
"context"
"testing"
"github.com/user-management-system/internal/auth"
"github.com/user-management-system/internal/domain"
"github.com/user-management-system/internal/repository"
"github.com/user-management-system/internal/service"
)
// =============================================================================
// UserService CRUD Tests - Phase 1 (Simplified)
// =============================================================================
func TestUserService_List(t *testing.T) {
env := setupAuthTestEnv(t)
if env == nil {
return
}
ctx := context.Background()
t.Run("List users", func(t *testing.T) {
// Create multiple users
for i := 0; i < 3; i++ {
user := &domain.User{
Username: "listuser_" + string(rune('a'+i)),
Password: "$2a$10$dummyhash",
Status: domain.UserStatusActive,
}
env.userSvc.Create(ctx, user)
}
// List
users, total, err := env.userSvc.List(ctx, 0, 10)
if err != nil {
t.Fatalf("List failed: %v", err)
}
if len(users) == 0 {
t.Error("Expected users to be returned")
}
if total < 3 {
t.Errorf("Expected total >= 3, got %d", total)
}
})
t.Run("List users with pagination", func(t *testing.T) {
users, total, err := env.userSvc.List(ctx, 0, 2)
if err != nil {
t.Fatalf("List with pagination failed: %v", err)
}
if len(users) > 2 {
t.Errorf("Expected max 2 users, got %d", len(users))
}
if total == 0 {
t.Error("Expected total > 0")
}
})
t.Run("List users with invalid pagination", func(t *testing.T) {
// Test with negative offset
_, _, err := env.userSvc.List(ctx, -1, 10)
if err != nil {
t.Errorf("List with negative offset should handle it gracefully: %v", err)
}
// Test with zero limit
_, _, err = env.userSvc.List(ctx, 0, 0)
if err != nil {
t.Errorf("List with zero limit should handle it gracefully: %v", err)
}
})
}
func TestUserService_GetByID(t *testing.T) {
env := setupAuthTestEnv(t)
if env == nil {
return
}
ctx := context.Background()
t.Run("Get user by ID success", func(t *testing.T) {
// Create user
user := &domain.User{
Username: "getbyid",
Password: "$2a$10$dummyhash",
Status: domain.UserStatusActive,
}
err := env.userSvc.Create(ctx, user)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
// Get by ID
found, err := env.userSvc.GetByID(ctx, user.ID)
if err != nil {
t.Fatalf("GetByID failed: %v", err)
}
if found.Username != "getbyid" {
t.Errorf("Expected username 'getbyid', got %s", found.Username)
}
})
t.Run("Get user by ID not found", func(t *testing.T) {
_, err := env.userSvc.GetByID(ctx, 99999)
if err == nil {
t.Error("Expected error for non-existent user")
}
})
t.Run("Get user by ID with zero ID", func(t *testing.T) {
_, err := env.userSvc.GetByID(ctx, 0)
if err == nil {
t.Error("Expected error for zero ID")
}
})
}
func TestUserService_Update(t *testing.T) {
env := setupAuthTestEnv(t)
if env == nil {
return
}
ctx := context.Background()
t.Run("Update user success", func(t *testing.T) {
// Create user
user := &domain.User{
Username: "updateuser",
Password: "$2a$10$dummyhash",
Status: domain.UserStatusActive,
Nickname: "Old Nickname",
}
err := env.userSvc.Create(ctx, user)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
// Update
user.Nickname = "New Nickname"
err = env.userSvc.Update(ctx, user)
if err != nil {
t.Fatalf("Update failed: %v", err)
}
// Verify
updated, _ := env.userSvc.GetByID(ctx, user.ID)
if updated.Nickname != "New Nickname" {
t.Errorf("Expected nickname 'New Nickname', got %s", updated.Nickname)
}
})
t.Run("Update non-existent user", func(t *testing.T) {
user := &domain.User{
ID: 99999,
Username: "nonexistent",
Password: "$2a$10$dummyhash",
Status: domain.UserStatusActive,
}
err := env.userSvc.Update(ctx, user)
// 实际实现可能静默处理
_ = err
t.Logf("Update non-existent user returned: %v", err)
})
}
func TestUserService_UpdateStatus(t *testing.T) {
env := setupAuthTestEnv(t)
if env == nil {
return
}
ctx := context.Background()
t.Run("Update status success", func(t *testing.T) {
// Create user
user := &domain.User{
Username: "statususer",
Password: "$2a$10$dummyhash",
Status: domain.UserStatusActive,
}
err := env.userSvc.Create(ctx, user)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
// Update status to locked
err = env.userSvc.UpdateStatus(ctx, user.ID, domain.UserStatusLocked)
if err != nil {
t.Fatalf("UpdateStatus failed: %v", err)
}
// Verify
updated, _ := env.userSvc.GetByID(ctx, user.ID)
if updated.Status != domain.UserStatusLocked {
t.Errorf("Expected status %d, got %d", domain.UserStatusLocked, updated.Status)
}
})
t.Run("Update status for non-existent user", func(t *testing.T) {
err := env.userSvc.UpdateStatus(ctx, 99999, domain.UserStatusLocked)
// 实际实现可能静默处理
_ = err
t.Logf("UpdateStatus non-existent user returned: %v", err)
})
}
func TestUserService_Delete(t *testing.T) {
env := setupAuthTestEnv(t)
if env == nil {
return
}
ctx := context.Background()
t.Run("Delete user success", func(t *testing.T) {
// Create user
user := &domain.User{
Username: "deleteuser",
Password: "$2a$10$dummyhash",
Status: domain.UserStatusActive,
}
err := env.userSvc.Create(ctx, user)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
// Delete
err = env.userSvc.Delete(ctx, user.ID)
if err != nil {
t.Fatalf("Delete failed: %v", err)
}
// Verify deletion
_, err = env.userSvc.GetByID(ctx, user.ID)
if err == nil {
t.Error("Expected error for deleted user")
}
})
t.Run("Delete non-existent user", func(t *testing.T) {
err := env.userSvc.Delete(ctx, 99999)
// 实际实现可能静默处理
_ = err
t.Logf("Delete non-existent user returned: %v", err)
})
}
func TestUserService_ChangePassword(t *testing.T) {
env := setupAuthTestEnv(t)
if env == nil {
return
}
ctx := context.Background()
t.Run("Change password success", func(t *testing.T) {
// Create user with known password
hashedPassword, _ := auth.HashPassword("OldPassword123!")
user := &domain.User{
Username: "changepwd",
Password: hashedPassword,
Status: domain.UserStatusActive,
}
env.userSvc.Create(ctx, user)
// Change password
err := env.userSvc.ChangePassword(ctx, user.ID, "OldPassword123!", "NewPassword456!")
if err != nil {
t.Fatalf("ChangePassword failed: %v", err)
}
// Verify new password works
updated, _ := env.userSvc.GetByID(ctx, user.ID)
if !auth.VerifyPassword(updated.Password, "NewPassword456!") {
t.Error("New password verification failed")
}
})
t.Run("Change password with wrong old password", func(t *testing.T) {
hashedPassword, _ := auth.HashPassword("CorrectPassword123!")
user := &domain.User{
Username: "wrongpwd",
Password: hashedPassword,
Status: domain.UserStatusActive,
}
env.userSvc.Create(ctx, user)
err := env.userSvc.ChangePassword(ctx, user.ID, "WrongPassword!", "NewPassword456!")
if err == nil {
t.Error("Expected error for wrong old password")
}
})
t.Run("Change password for non-existent user", func(t *testing.T) {
err := env.userSvc.ChangePassword(ctx, 99999, "old", "NewPassword123!")
if err == nil {
t.Error("Expected error for non-existent user")
}
})
t.Run("Change password with empty old password", func(t *testing.T) {
user := &domain.User{
Username: "emptypwd",
Password: "$2a$10$dummyhash",
Status: domain.UserStatusActive,
}
env.userSvc.Create(ctx, user)
err := env.userSvc.ChangePassword(ctx, user.ID, "", "NewPassword123!")
if err == nil {
t.Error("Expected error for empty old password")
}
})
t.Run("Change password with empty new password", func(t *testing.T) {
hashedPassword, _ := auth.HashPassword("OldPassword123!")
user := &domain.User{
Username: "emptynewpwd",
Password: hashedPassword,
Status: domain.UserStatusActive,
}
env.userSvc.Create(ctx, user)
err := env.userSvc.ChangePassword(ctx, user.ID, "OldPassword123!", "")
if err == nil {
t.Error("Expected error for empty new password")
}
})
t.Run("Change password with weak new password", func(t *testing.T) {
hashedPassword, _ := auth.HashPassword("OldPassword123!")
user := &domain.User{
Username: "weakpwd",
Password: hashedPassword,
Status: domain.UserStatusActive,
}
env.userSvc.Create(ctx, user)
err := env.userSvc.ChangePassword(ctx, user.ID, "OldPassword123!", "123")
if err == nil {
t.Error("Expected error for weak new password")
}
})
t.Run("Change password persists history synchronously", func(t *testing.T) {
hashedPassword, _ := auth.HashPassword("HistoryOld123!")
user := &domain.User{
Username: "historysync",
Password: hashedPassword,
Status: domain.UserStatusActive,
}
env.userSvc.Create(ctx, user)
if err := env.userSvc.ChangePassword(ctx, user.ID, "HistoryOld123!", "HistoryNew456!"); err != nil {
t.Fatalf("ChangePassword failed: %v", err)
}
historyRepo := repository.NewPasswordHistoryRepository(env.db)
history, err := historyRepo.GetByUserID(ctx, user.ID, 10)
if err != nil {
t.Fatalf("GetByUserID failed: %v", err)
}
if len(history) == 0 {
t.Fatal("expected password history to be written synchronously")
}
if !auth.VerifyPassword(history[0].PasswordHash, "HistoryNew456!") {
t.Fatal("latest password history hash does not match new password")
}
})
}
func TestUserService_BatchUpdateStatus(t *testing.T) {
env := setupAuthTestEnv(t)
if env == nil {
return
}
ctx := context.Background()
// Create test users
user1 := &domain.User{Username: "batch1", Password: "$2a$10$hash", Status: domain.UserStatusActive}
user2 := &domain.User{Username: "batch2", Password: "$2a$10$hash", Status: domain.UserStatusActive}
env.userSvc.Create(ctx, user1)
env.userSvc.Create(ctx, user2)
t.Run("Batch update status", func(t *testing.T) {
_, err := env.userSvc.BatchUpdateStatus(ctx, &service.BatchUpdateStatusRequest{
IDs: []int64{user1.ID, user2.ID},
Status: domain.UserStatusLocked,
})
if err != nil {
t.Fatalf("BatchUpdateStatus failed: %v", err)
}
updated1, _ := env.userSvc.GetByID(ctx, user1.ID)
if updated1.Status != domain.UserStatusLocked {
t.Error("Expected user1 status to be locked")
}
})
}
func TestUserService_Create(t *testing.T) {
env := setupAuthTestEnv(t)
if env == nil {
return
}
ctx := context.Background()
t.Run("Create user success", func(t *testing.T) {
email := "create@test.com"
user := &domain.User{
Username: "createuser",
Password: "$2a$10$hash",
Email: &email,
Status: domain.UserStatusActive,
}
err := env.userSvc.Create(ctx, user)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
if user.ID == 0 {
t.Error("Expected user ID to be set")
}
})
t.Run("Create user with duplicate username", func(t *testing.T) {
user1 := &domain.User{Username: "dupcreate", Password: "$2a$10$hash", Status: domain.UserStatusActive}
env.userSvc.Create(ctx, user1)
user2 := &domain.User{Username: "dupcreate", Password: "$2a$10$hash", Status: domain.UserStatusActive}
err := env.userSvc.Create(ctx, user2)
if err == nil {
t.Error("Expected error for duplicate username")
}
})
}
func TestUserService_GetByEmail(t *testing.T) {
env := setupAuthTestEnv(t)
if env == nil {
return
}
ctx := context.Background()
t.Run("Get user by email", func(t *testing.T) {
email := "getbyemail@test.com"
user := &domain.User{
Username: "emailuser",
Password: "$2a$10$hash",
Email: &email,
Status: domain.UserStatusActive,
}
env.userSvc.Create(ctx, user)
found, err := env.userSvc.GetByEmail(ctx, "getbyemail@test.com")
if err != nil {
t.Fatalf("GetByEmail failed: %v", err)
}
if found.Username != "emailuser" {
t.Errorf("Expected username 'emailuser', got %s", found.Username)
}
})
t.Run("Get user by non-existent email", func(t *testing.T) {
_, err := env.userSvc.GetByEmail(ctx, "nonexistent@test.com")
if err == nil {
t.Error("Expected error for non-existent email")
}
})
}