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)
896 lines
26 KiB
Go
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)
|
|
}
|