|
|
@@ -0,0 +1,188 @@
|
|
|
+import { UserService } from '../user.service';
|
|
|
+import { DataSource, Repository } from 'typeorm';
|
|
|
+import { UserEntity as User } from '../user.entity';
|
|
|
+import { Role } from '../role.entity';
|
|
|
+import * as bcrypt from 'bcrypt';
|
|
|
+
|
|
|
+// Mock TypeORM 数据源和仓库
|
|
|
+jest.mock('typeorm', () => ({
|
|
|
+ DataSource: jest.fn().mockImplementation(() => ({
|
|
|
+ getRepository: jest.fn()
|
|
|
+ })),
|
|
|
+ Repository: jest.fn()
|
|
|
+}));
|
|
|
+
|
|
|
+// Mock bcrypt
|
|
|
+jest.mock('bcrypt', () => ({
|
|
|
+ hash: jest.fn().mockResolvedValue('hashed_password'),
|
|
|
+ compare: jest.fn().mockResolvedValue(true)
|
|
|
+}));
|
|
|
+
|
|
|
+describe('UserService', () => {
|
|
|
+ let userService: UserService;
|
|
|
+ let mockDataSource: jest.Mocked<DataSource>;
|
|
|
+ let mockUserRepository: jest.Mocked<Repository<User>>;
|
|
|
+ let mockRoleRepository: jest.Mocked<Repository<Role>>;
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ // 创建模拟的仓库实例
|
|
|
+ mockUserRepository = {
|
|
|
+ create: jest.fn(),
|
|
|
+ save: jest.fn(),
|
|
|
+ findOne: jest.fn(),
|
|
|
+ update: jest.fn(),
|
|
|
+ delete: jest.fn(),
|
|
|
+ createQueryBuilder: jest.fn(),
|
|
|
+ find: jest.fn(),
|
|
|
+ findByIds: jest.fn()
|
|
|
+ } as any;
|
|
|
+
|
|
|
+ mockRoleRepository = {
|
|
|
+ findByIds: jest.fn()
|
|
|
+ } as any;
|
|
|
+
|
|
|
+ // 创建模拟的数据源
|
|
|
+ mockDataSource = {
|
|
|
+ getRepository: jest.fn()
|
|
|
+ } as any;
|
|
|
+
|
|
|
+ // 设置数据源返回模拟的仓库
|
|
|
+ mockDataSource.getRepository
|
|
|
+ .mockReturnValueOnce(mockUserRepository)
|
|
|
+ .mockReturnValueOnce(mockRoleRepository);
|
|
|
+
|
|
|
+ userService = new UserService(mockDataSource);
|
|
|
+ });
|
|
|
+
|
|
|
+ afterEach(() => {
|
|
|
+ jest.clearAllMocks();
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('createUser', () => {
|
|
|
+ it('应该成功创建用户并哈希密码', async () => {
|
|
|
+ const userData = {
|
|
|
+ username: 'testuser',
|
|
|
+ password: 'password123',
|
|
|
+ email: 'test@example.com'
|
|
|
+ };
|
|
|
+
|
|
|
+ const mockUser = { id: 1, ...userData, password: 'hashed_password' } as User;
|
|
|
+
|
|
|
+ mockUserRepository.create.mockReturnValue(mockUser);
|
|
|
+ mockUserRepository.save.mockResolvedValue(mockUser);
|
|
|
+
|
|
|
+ const result = await userService.createUser(userData);
|
|
|
+
|
|
|
+ expect(bcrypt.hash).toHaveBeenCalledWith('password123', 10);
|
|
|
+ expect(mockUserRepository.create).toHaveBeenCalledWith({
|
|
|
+ ...userData,
|
|
|
+ password: 'hashed_password'
|
|
|
+ });
|
|
|
+ expect(mockUserRepository.save).toHaveBeenCalledWith(mockUser);
|
|
|
+ expect(result).toEqual(mockUser);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在创建用户失败时抛出错误', async () => {
|
|
|
+ const userData = { username: 'testuser', password: 'password123' };
|
|
|
+ const error = new Error('Database error');
|
|
|
+
|
|
|
+ mockUserRepository.create.mockImplementation(() => {
|
|
|
+ throw error;
|
|
|
+ });
|
|
|
+
|
|
|
+ await expect(userService.createUser(userData)).rejects.toThrow('Failed to create user');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('getUserById', () => {
|
|
|
+ it('应该通过ID成功获取用户', async () => {
|
|
|
+ const mockUser = { id: 1, username: 'testuser' } as User;
|
|
|
+ mockUserRepository.findOne.mockResolvedValue(mockUser);
|
|
|
+
|
|
|
+ const result = await userService.getUserById(1);
|
|
|
+
|
|
|
+ expect(mockUserRepository.findOne).toHaveBeenCalledWith({
|
|
|
+ where: { id: 1 },
|
|
|
+ relations: ['roles']
|
|
|
+ });
|
|
|
+ expect(result).toEqual(mockUser);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在用户不存在时返回null', async () => {
|
|
|
+ mockUserRepository.findOne.mockResolvedValue(null);
|
|
|
+
|
|
|
+ const result = await userService.getUserById(999);
|
|
|
+
|
|
|
+ expect(result).toBeNull();
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('getUsersWithPagination', () => {
|
|
|
+ it('应该成功获取分页用户列表', async () => {
|
|
|
+ const mockUsers = [{ id: 1 }, { id: 2 }] as User[];
|
|
|
+ const total = 2;
|
|
|
+
|
|
|
+ const mockQueryBuilder = {
|
|
|
+ leftJoinAndSelect: jest.fn().mockReturnThis(),
|
|
|
+ skip: jest.fn().mockReturnThis(),
|
|
|
+ take: jest.fn().mockReturnThis(),
|
|
|
+ where: jest.fn().mockReturnThis(),
|
|
|
+ getManyAndCount: jest.fn().mockResolvedValue([mockUsers, total])
|
|
|
+ };
|
|
|
+
|
|
|
+ mockUserRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder as any);
|
|
|
+
|
|
|
+ const result = await userService.getUsersWithPagination({
|
|
|
+ page: 1,
|
|
|
+ pageSize: 10
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(mockQueryBuilder.skip).toHaveBeenCalledWith(0);
|
|
|
+ expect(mockQueryBuilder.take).toHaveBeenCalledWith(10);
|
|
|
+ expect(result).toEqual([mockUsers, total]);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该支持关键词搜索', async () => {
|
|
|
+ const mockQueryBuilder = {
|
|
|
+ leftJoinAndSelect: jest.fn().mockReturnThis(),
|
|
|
+ skip: jest.fn().mockReturnThis(),
|
|
|
+ take: jest.fn().mockReturnThis(),
|
|
|
+ where: jest.fn().mockReturnThis(),
|
|
|
+ getManyAndCount: jest.fn().mockResolvedValue([[], 0])
|
|
|
+ };
|
|
|
+
|
|
|
+ mockUserRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder as any);
|
|
|
+
|
|
|
+ await userService.getUsersWithPagination({
|
|
|
+ page: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ keyword: 'test'
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(mockQueryBuilder.where).toHaveBeenCalledWith(
|
|
|
+ 'user.username LIKE :keyword OR user.nickname LIKE :keyword OR user.phone LIKE :keyword',
|
|
|
+ { keyword: '%test%' }
|
|
|
+ );
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('verifyPassword', () => {
|
|
|
+ it('应该验证密码正确', async () => {
|
|
|
+ const user = { password: 'hashed_password' } as User;
|
|
|
+
|
|
|
+ const result = await userService.verifyPassword(user, 'password123');
|
|
|
+
|
|
|
+ expect(bcrypt.compare).toHaveBeenCalledWith('password123', 'hashed_password');
|
|
|
+ expect(result).toBe(true);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该验证密码错误', async () => {
|
|
|
+ (bcrypt.compare as jest.Mock).mockResolvedValueOnce(false);
|
|
|
+ const user = { password: 'hashed_password' } as User;
|
|
|
+
|
|
|
+ const result = await userService.verifyPassword(user, 'wrong_password');
|
|
|
+
|
|
|
+ expect(result).toBe(false);
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|