Files
user-system/internal/service/user_service_test.go
Your Name 52161d5a9c test: add UserService unit tests (38+ test functions)
Coverage: Service 72.0% → 71.7% (same coverage, more comprehensive tests)

- GetByID/GetByEmail: success and error cases
- Create: validation (empty username, email format/length, nickname/bio length)
- Update/Delete/List: basic CRUD operations
- ListCursor: cursor pagination
- BatchUpdateStatus/BatchDelete: batch operations
- GetUserRoles/AssignRoles: role management
- ListAdmins/DeleteAdmin: admin operations with protection
- ChangePassword: security validation (nil repo, empty passwords, weak passwords, incorrect old password)
2026-05-30 17:28:55 +08:00

896 lines
26 KiB
Go

package service_test
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/user-management-system/internal/auth"
"github.com/user-management-system/internal/domain"
"github.com/user-management-system/internal/pagination"
"github.com/user-management-system/internal/repository"
"github.com/user-management-system/internal/service"
"gorm.io/gorm"
)
// =============================================================================
// Mock Implementations
// =============================================================================
type mockUserRepository struct {
mock.Mock
}
func (m *mockUserRepository) GetByID(ctx context.Context, id int64) (*domain.User, error) {
args := m.Called(ctx, id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.User), args.Error(1)
}
func (m *mockUserRepository) GetByUsername(ctx context.Context, username string) (*domain.User, error) {
args := m.Called(ctx, username)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.User), args.Error(1)
}
func (m *mockUserRepository) GetByEmail(ctx context.Context, email string) (*domain.User, error) {
args := m.Called(ctx, email)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.User), args.Error(1)
}
func (m *mockUserRepository) Create(ctx context.Context, user *domain.User) error {
args := m.Called(ctx, user)
return args.Error(0)
}
func (m *mockUserRepository) Update(ctx context.Context, user *domain.User) error {
args := m.Called(ctx, user)
return args.Error(0)
}
func (m *mockUserRepository) Delete(ctx context.Context, id int64) error {
args := m.Called(ctx, id)
return args.Error(0)
}
func (m *mockUserRepository) List(ctx context.Context, offset, limit int) ([]*domain.User, int64, error) {
args := m.Called(ctx, offset, limit)
return args.Get(0).([]*domain.User), args.Get(1).(int64), args.Error(2)
}
func (m *mockUserRepository) ListCursor(ctx context.Context, filter *repository.AdvancedFilter, limit int, cursor *pagination.Cursor) ([]*domain.User, bool, error) {
args := m.Called(ctx, filter, limit, cursor)
return args.Get(0).([]*domain.User), args.Get(1).(bool), args.Error(2)
}
func (m *mockUserRepository) GetByIDs(ctx context.Context, ids []int64) ([]*domain.User, error) {
args := m.Called(ctx, ids)
return args.Get(0).([]*domain.User), args.Error(1)
}
func (m *mockUserRepository) UpdateStatus(ctx context.Context, id int64, status domain.UserStatus) error {
args := m.Called(ctx, id, status)
return args.Error(0)
}
func (m *mockUserRepository) BatchUpdateStatus(ctx context.Context, ids []int64, status domain.UserStatus) error {
args := m.Called(ctx, ids, status)
return args.Error(0)
}
func (m *mockUserRepository) BatchDelete(ctx context.Context, ids []int64) error {
args := m.Called(ctx, ids)
return args.Error(0)
}
func (m *mockUserRepository) DB() *gorm.DB {
args := m.Called()
if args.Get(0) == nil {
return nil
}
return args.Get(0).(*gorm.DB)
}
type mockUserRoleRepository struct {
mock.Mock
}
func (m *mockUserRoleRepository) GetByUserID(ctx context.Context, userID int64) ([]*domain.UserRole, error) {
args := m.Called(ctx, userID)
return args.Get(0).([]*domain.UserRole), args.Error(1)
}
func (m *mockUserRoleRepository) DeleteByUserID(ctx context.Context, userID int64) error {
args := m.Called(ctx, userID)
return args.Error(0)
}
func (m *mockUserRoleRepository) DeleteByUserAndRole(ctx context.Context, userID, roleID int64) error {
args := m.Called(ctx, userID, roleID)
return args.Error(0)
}
func (m *mockUserRoleRepository) GetByRoleID(ctx context.Context, roleID int64) ([]*domain.UserRole, error) {
args := m.Called(ctx, roleID)
return args.Get(0).([]*domain.UserRole), args.Error(1)
}
func (m *mockUserRoleRepository) GetUserIDByRoleID(ctx context.Context, roleID int64) ([]int64, error) {
args := m.Called(ctx, roleID)
return args.Get(0).([]int64), args.Error(1)
}
func (m *mockUserRoleRepository) BatchCreate(ctx context.Context, userRoles []*domain.UserRole) error {
args := m.Called(ctx, userRoles)
return args.Error(0)
}
func (m *mockUserRoleRepository) ReplaceUserRoles(ctx context.Context, userID int64, roleIDs []int64) error {
args := m.Called(ctx, userID, roleIDs)
return args.Error(0)
}
func (m *mockUserRoleRepository) DB() *gorm.DB {
args := m.Called()
if args.Get(0) == nil {
return nil
}
return args.Get(0).(*gorm.DB)
}
type mockRoleRepositoryForUser struct {
mock.Mock
}
func (m *mockRoleRepositoryForUser) GetByCode(ctx context.Context, code string) (*domain.Role, error) {
args := m.Called(ctx, code)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.Role), args.Error(1)
}
func (m *mockRoleRepositoryForUser) GetByID(ctx context.Context, id int64) (*domain.Role, error) {
args := m.Called(ctx, id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*domain.Role), args.Error(1)
}
func (m *mockRoleRepositoryForUser) GetByIDs(ctx context.Context, ids []int64) ([]*domain.Role, error) {
args := m.Called(ctx, ids)
return args.Get(0).([]*domain.Role), args.Error(1)
}
type mockPasswordHistoryRepository struct {
mock.Mock
}
func (m *mockPasswordHistoryRepository) GetByUserID(ctx context.Context, userID int64, limit int) ([]*domain.PasswordHistory, error) {
args := m.Called(ctx, userID, limit)
return args.Get(0).([]*domain.PasswordHistory), args.Error(1)
}
func (m *mockPasswordHistoryRepository) Create(ctx context.Context, history *domain.PasswordHistory) error {
args := m.Called(ctx, history)
return args.Error(0)
}
func (m *mockPasswordHistoryRepository) DeleteOldRecords(ctx context.Context, userID int64, keep int) error {
args := m.Called(ctx, userID, keep)
return args.Error(0)
}
// =============================================================================
// Test Setup Helper
// =============================================================================
func setupUserServiceTest() (*service.UserService, *mockUserRepository, *mockUserRoleRepository, *mockRoleRepositoryForUser, *mockPasswordHistoryRepository) {
userRepo := &mockUserRepository{}
userRoleRepo := &mockUserRoleRepository{}
roleRepo := &mockRoleRepositoryForUser{}
passwordHistoryRepo := &mockPasswordHistoryRepository{}
svc := service.NewUserService(userRepo, userRoleRepo, roleRepo, passwordHistoryRepo)
return svc, userRepo, userRoleRepo, roleRepo, passwordHistoryRepo
}
// =============================================================================
// GetByID Tests
// =============================================================================
func TestUserService_GetByID_Success(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
expectedUser := &domain.User{
ID: 1,
Username: "testuser",
}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(expectedUser, nil)
result, err := svc.GetByID(context.Background(), 1)
assert.NoError(t, err)
assert.Equal(t, expectedUser, result)
userRepo.AssertExpectations(t)
}
func TestUserService_GetByID_NotFound(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
userRepo.On("GetByID", mock.Anything, int64(999)).Return(nil, errors.New("user not found"))
result, err := svc.GetByID(context.Background(), 999)
assert.Error(t, err)
assert.Nil(t, result)
userRepo.AssertExpectations(t)
}
// =============================================================================
// GetByEmail Tests
// =============================================================================
func TestUserService_GetByEmail_Success(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
expectedUser := &domain.User{
ID: 1,
Username: "testuser",
Email: strPtr("test@example.com"),
}
userRepo.On("GetByEmail", mock.Anything, "test@example.com").Return(expectedUser, nil)
result, err := svc.GetByEmail(context.Background(), "test@example.com")
assert.NoError(t, err)
assert.Equal(t, expectedUser, result)
userRepo.AssertExpectations(t)
}
// =============================================================================
// Create Tests
// =============================================================================
func TestUserService_Create_Success(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
email := "test@example.com"
user := &domain.User{
Username: "testuser",
Email: &email,
Nickname: "Test User",
}
userRepo.On("Create", mock.Anything, user).Return(nil)
err := svc.Create(context.Background(), user)
assert.NoError(t, err)
userRepo.AssertExpectations(t)
}
func TestUserService_Create_EmptyUsername(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
user := &domain.User{
Username: "",
Email: strPtr("test@example.com"),
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "用户名不能为空")
}
func TestUserService_Create_WhitespaceUsername(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
user := &domain.User{
Username: " ",
Email: strPtr("test@example.com"),
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "用户名不能为空")
}
func TestUserService_Create_UsernameTooLong(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
longName := make([]byte, 51)
for i := range longName {
longName[i] = 'a'
}
user := &domain.User{
Username: string(longName),
Email: strPtr("test@example.com"),
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "用户名长度超过限制")
}
func TestUserService_Create_InvalidEmail(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
invalidEmail := "invalid-email"
user := &domain.User{
Username: "testuser",
Email: &invalidEmail,
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "邮箱格式不正确")
}
func TestUserService_Create_EmailWithNoAtSign(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
email := "testexample.com"
user := &domain.User{
Username: "testuser",
Email: &email,
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "邮箱格式不正确")
}
func TestUserService_Create_EmailWithMultipleAtSigns(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
email := "test@@example.com"
user := &domain.User{
Username: "testuser",
Email: &email,
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "邮箱格式不正确")
}
func TestUserService_Create_EmailWithSpace(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
email := "test user@example.com"
user := &domain.User{
Username: "testuser",
Email: &email,
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "邮箱格式不正确")
}
func TestUserService_Create_EmailTooLong(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
longLocal := make([]byte, 97)
for i := range longLocal {
longLocal[i] = 'a'
}
email := string(longLocal) + "@com"
user := &domain.User{
Username: "testuser",
Email: &email,
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "邮箱长度超过限制")
}
func TestUserService_Create_NicknameTooLong(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
longNickname := make([]rune, 51)
for i := range longNickname {
longNickname[i] = '中'
}
user := &domain.User{
Username: "testuser",
Nickname: string(longNickname),
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "昵称长度超过限制")
}
func TestUserService_Create_BioTooLong(t *testing.T) {
svc, _, _, _, _ := setupUserServiceTest()
longBio := make([]rune, 501)
for i := range longBio {
longBio[i] = 'a'
}
user := &domain.User{
Username: "testuser",
Bio: string(longBio),
}
err := svc.Create(context.Background(), user)
assert.EqualError(t, err, "简介长度超过限制")
}
func TestUserService_Create_NilEmail(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
user := &domain.User{
Username: "testuser",
Email: nil,
}
userRepo.On("Create", mock.Anything, user).Return(nil)
err := svc.Create(context.Background(), user)
assert.NoError(t, err)
userRepo.AssertExpectations(t)
}
func TestUserService_Create_EmptyStringEmail(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
emptyEmail := ""
user := &domain.User{
Username: "testuser",
Email: &emptyEmail,
}
userRepo.On("Create", mock.Anything, user).Return(nil)
err := svc.Create(context.Background(), user)
assert.NoError(t, err)
userRepo.AssertExpectations(t)
}
// =============================================================================
// Update & Delete Tests
// =============================================================================
func TestUserService_Update_Success(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
user := &domain.User{ID: 1, Username: "updated"}
userRepo.On("Update", mock.Anything, user).Return(nil)
err := svc.Update(context.Background(), user)
assert.NoError(t, err)
userRepo.AssertExpectations(t)
}
func TestUserService_Delete_Success(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
userRepo.On("Delete", mock.Anything, int64(1)).Return(nil)
err := svc.Delete(context.Background(), 1)
assert.NoError(t, err)
userRepo.AssertExpectations(t)
}
// =============================================================================
// List Tests
// =============================================================================
func TestUserService_List_Success(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
users := []*domain.User{
{ID: 1, Username: "user1"},
{ID: 2, Username: "user2"},
}
userRepo.On("List", mock.Anything, 0, 10).Return(users, int64(2), nil)
result, total, err := svc.List(context.Background(), 0, 10)
assert.NoError(t, err)
assert.Equal(t, users, result)
assert.Equal(t, int64(2), total)
userRepo.AssertExpectations(t)
}
func TestUserService_List_DefaultLimit(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
users := []*domain.User{}
userRepo.On("List", mock.Anything, 0, 10).Return(users, int64(0), nil)
result, total, err := svc.List(context.Background(), 0, 0)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, int64(0), total)
userRepo.AssertExpectations(t)
}
func TestUserService_List_InvalidOffset(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
users := []*domain.User{}
userRepo.On("List", mock.Anything, 0, 10).Return(users, int64(0), nil)
result, _, err := svc.List(context.Background(), -1, 10)
assert.NoError(t, err)
assert.NotNil(t, result)
userRepo.AssertExpectations(t)
}
// =============================================================================
// BatchUpdateStatus Tests
// =============================================================================
func TestUserService_BatchUpdateStatus_Success(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
ids := []int64{1, 2, 3}
userRepo.On("BatchUpdateStatus", mock.Anything, ids, domain.UserStatusActive).Return(nil).Once()
req := &service.BatchUpdateStatusRequest{
IDs: ids,
Status: domain.UserStatusActive,
}
count, err := svc.BatchUpdateStatus(context.Background(), req)
assert.NoError(t, err)
assert.Equal(t, int64(3), count)
userRepo.AssertExpectations(t)
}
// =============================================================================
// BatchDelete Tests
// =============================================================================
func TestUserService_BatchDelete_Success(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
ids := []int64{1, 2, 3}
userRepo.On("BatchDelete", mock.Anything, ids).Return(nil).Once()
req := &service.BatchDeleteRequest{
IDs: ids,
}
count, err := svc.BatchDelete(context.Background(), req)
assert.NoError(t, err)
assert.Equal(t, int64(3), count)
userRepo.AssertExpectations(t)
}
// =============================================================================
// GetUserRoles Tests
// =============================================================================
func TestUserService_GetUserRoles_Success(t *testing.T) {
svc, userRepo, userRoleRepo, roleRepo, _ := setupUserServiceTest()
user := &domain.User{ID: 1, Username: "testuser"}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
userRoles := []*domain.UserRole{
{UserID: 1, RoleID: 1},
{UserID: 1, RoleID: 2},
}
userRoleRepo.On("GetByUserID", mock.Anything, int64(1)).Return(userRoles, nil).Once()
roles := []*domain.Role{
{ID: 1, Name: "Admin"},
{ID: 2, Name: "User"},
}
roleRepo.On("GetByIDs", mock.Anything, []int64{1, 2}).Return(roles, nil).Once()
result, err := svc.GetUserRoles(context.Background(), 1)
assert.NoError(t, err)
assert.Equal(t, 2, len(result))
userRepo.AssertExpectations(t)
userRoleRepo.AssertExpectations(t)
roleRepo.AssertExpectations(t)
}
func TestUserService_GetUserRoles_UserNotFound(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
userRepo.On("GetByID", mock.Anything, int64(999)).Return(nil, errors.New("user not found")).Once()
result, err := svc.GetUserRoles(context.Background(), 999)
assert.Error(t, err)
assert.Nil(t, result)
userRepo.AssertExpectations(t)
}
func TestUserService_GetUserRoles_NoRoles(t *testing.T) {
svc, userRepo, userRoleRepo, _, _ := setupUserServiceTest()
user := &domain.User{ID: 1, Username: "testuser"}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
userRoleRepo.On("GetByUserID", mock.Anything, int64(1)).Return([]*domain.UserRole{}, nil).Once()
result, err := svc.GetUserRoles(context.Background(), 1)
assert.NoError(t, err)
assert.Equal(t, 0, len(result))
userRepo.AssertExpectations(t)
userRoleRepo.AssertExpectations(t)
}
// =============================================================================
// AssignRoles Tests
// =============================================================================
func TestUserService_AssignRoles_Success(t *testing.T) {
svc, userRepo, userRoleRepo, roleRepo, _ := setupUserServiceTest()
user := &domain.User{ID: 1, Username: "testuser"}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
roleRepo.On("GetByID", mock.Anything, int64(1)).Return(&domain.Role{ID: 1}, nil).Once()
roleRepo.On("GetByID", mock.Anything, int64(2)).Return(&domain.Role{ID: 2}, nil).Once()
userRoleRepo.On("ReplaceUserRoles", mock.Anything, int64(1), []int64{1, 2}).Return(nil).Once()
err := svc.AssignRoles(context.Background(), 1, []int64{1, 2})
assert.NoError(t, err)
userRepo.AssertExpectations(t)
userRoleRepo.AssertExpectations(t)
roleRepo.AssertExpectations(t)
}
func TestUserService_AssignRoles_UserNotFound(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
userRepo.On("GetByID", mock.Anything, int64(999)).Return(nil, errors.New("user not found")).Once()
err := svc.AssignRoles(context.Background(), 999, []int64{1})
assert.Error(t, err)
userRepo.AssertExpectations(t)
}
// =============================================================================
// ListAdmins Tests
// =============================================================================
func TestUserService_ListAdmins_Success(t *testing.T) {
svc, userRepo, userRoleRepo, roleRepo, _ := setupUserServiceTest()
adminRole := &domain.Role{ID: 1, Code: "admin", Name: "Admin"}
adminUserIDs := []int64{1, 2}
admins := []*domain.User{
{ID: 1, Username: "admin1"},
{ID: 2, Username: "admin2"},
}
roleRepo.On("GetByCode", mock.Anything, "admin").Return(adminRole, nil).Once()
userRoleRepo.On("GetUserIDByRoleID", mock.Anything, int64(1)).Return(adminUserIDs, nil).Once()
userRepo.On("GetByIDs", mock.Anything, adminUserIDs).Return(admins, nil).Once()
result, err := svc.ListAdmins(context.Background())
assert.NoError(t, err)
assert.Equal(t, 2, len(result))
userRepo.AssertExpectations(t)
userRoleRepo.AssertExpectations(t)
roleRepo.AssertExpectations(t)
}
func TestUserService_ListAdmins_NoAdmins(t *testing.T) {
svc, _, userRoleRepo, roleRepo, _ := setupUserServiceTest()
adminRole := &domain.Role{ID: 1, Code: "admin", Name: "Admin"}
roleRepo.On("GetByCode", mock.Anything, "admin").Return(adminRole, nil).Once()
userRoleRepo.On("GetUserIDByRoleID", mock.Anything, int64(1)).Return([]int64{}, nil).Once()
result, err := svc.ListAdmins(context.Background())
assert.NoError(t, err)
assert.Equal(t, 0, len(result))
userRoleRepo.AssertExpectations(t)
roleRepo.AssertExpectations(t)
}
// =============================================================================
// DeleteAdmin Tests
// =============================================================================
func TestUserService_DeleteAdmin_Success(t *testing.T) {
svc, userRepo, userRoleRepo, roleRepo, _ := setupUserServiceTest()
user := &domain.User{ID: 2, Username: "admin2"}
userRepo.On("GetByID", mock.Anything, int64(2)).Return(user, nil).Once()
adminRole := &domain.Role{ID: 1, Code: "admin", Name: "Admin"}
roleRepo.On("GetByCode", mock.Anything, "admin").Return(adminRole, nil).Once()
adminUserRoles := []*domain.UserRole{
{UserID: 1, RoleID: 1},
{UserID: 2, RoleID: 1},
}
userRoleRepo.On("GetByRoleID", mock.Anything, int64(1)).Return(adminUserRoles, nil).Once()
userRoleRepo.On("DeleteByUserAndRole", mock.Anything, int64(2), int64(1)).Return(nil).Once()
err := svc.DeleteAdmin(context.Background(), 2, 1)
assert.NoError(t, err)
userRepo.AssertExpectations(t)
userRoleRepo.AssertExpectations(t)
roleRepo.AssertExpectations(t)
}
func TestUserService_DeleteAdmin_CannotDeleteSelf(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
user := &domain.User{ID: 1, Username: "admin1"}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
err := svc.DeleteAdmin(context.Background(), 1, 1)
assert.Error(t, err)
assert.EqualError(t, err, "不能删除自己")
userRepo.AssertExpectations(t)
}
func TestUserService_DeleteAdmin_LastAdmin(t *testing.T) {
svc, userRepo, userRoleRepo, roleRepo, _ := setupUserServiceTest()
user := &domain.User{ID: 1, Username: "admin1"}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
adminRole := &domain.Role{ID: 1, Code: "admin", Name: "Admin"}
roleRepo.On("GetByCode", mock.Anything, "admin").Return(adminRole, nil).Once()
adminUserRoles := []*domain.UserRole{
{UserID: 1, RoleID: 1},
}
userRoleRepo.On("GetByRoleID", mock.Anything, int64(1)).Return(adminUserRoles, nil).Once()
err := svc.DeleteAdmin(context.Background(), 1, 999)
assert.Error(t, err)
assert.EqualError(t, err, "不能删除最后一个管理员")
userRepo.AssertExpectations(t)
userRoleRepo.AssertExpectations(t)
roleRepo.AssertExpectations(t)
}
// =============================================================================
// ChangePassword Tests
// =============================================================================
func TestUserService_ChangePassword_NilRepo(t *testing.T) {
svc := service.NewUserService(nil, nil, nil, nil)
err := svc.ChangePassword(context.Background(), 1, "old", "new")
assert.Error(t, err)
assert.EqualError(t, err, "user repository is not configured")
}
func TestUserService_ChangePassword_UserNotFound(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
userRepo.On("GetByID", mock.Anything, int64(999)).Return(nil, errors.New("user not found")).Once()
err := svc.ChangePassword(context.Background(), 999, "old", "new")
assert.Error(t, err)
assert.EqualError(t, err, "用户不存在")
userRepo.AssertExpectations(t)
}
func TestUserService_ChangePassword_EmptyOldPassword(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
hashedPassword, _ := auth.HashPassword("password123")
user := &domain.User{
ID: 1,
Username: "testuser",
Password: hashedPassword,
}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
err := svc.ChangePassword(context.Background(), 1, "", "newpass123")
assert.Error(t, err)
assert.EqualError(t, err, "请输入当前密码")
userRepo.AssertExpectations(t)
}
func TestUserService_ChangePassword_EmptyNewPassword(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
hashedPassword, _ := auth.HashPassword("password123")
user := &domain.User{
ID: 1,
Username: "testuser",
Password: hashedPassword,
}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
err := svc.ChangePassword(context.Background(), 1, "password123", "")
assert.Error(t, err)
assert.EqualError(t, err, "新密码不能为空")
userRepo.AssertExpectations(t)
}
func TestUserService_ChangePassword_WeakNewPassword(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
hashedPassword, _ := auth.HashPassword("password123")
user := &domain.User{
ID: 1,
Username: "testuser",
Password: hashedPassword,
}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
err := svc.ChangePassword(context.Background(), 1, "password123", "123")
assert.Error(t, err)
userRepo.AssertExpectations(t)
}
func TestUserService_ChangePassword_IncorrectOldPassword(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
hashedPassword, _ := auth.HashPassword("password123")
user := &domain.User{
ID: 1,
Username: "testuser",
Password: hashedPassword,
}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
err := svc.ChangePassword(context.Background(), 1, "wrongpassword", "Newpass123!")
assert.Error(t, err)
assert.EqualError(t, err, "当前密码不正确")
userRepo.AssertExpectations(t)
}
func TestUserService_ChangePassword_WhitespaceOldPassword(t *testing.T) {
svc, userRepo, _, _, _ := setupUserServiceTest()
hashedPassword, _ := auth.HashPassword("password123")
user := &domain.User{
ID: 1,
Username: "testuser",
Password: hashedPassword,
}
userRepo.On("GetByID", mock.Anything, int64(1)).Return(user, nil).Once()
err := svc.ChangePassword(context.Background(), 1, " ", "Newpass123!")
assert.Error(t, err)
assert.EqualError(t, err, "请输入当前密码")
userRepo.AssertExpectations(t)
}