import { UserService } from '../../../src/services/user/user.service'; // Mock Parse SDK const mockQuery = { equalTo: jest.fn().mockReturnThis(), first: jest.fn(), find: jest.fn(), count: jest.fn(), get: jest.fn(), }; // Mock Parse SDK jest.mock('parse/node', () => ({ User: Object.assign( jest.fn().mockImplementation(() => ({ set: jest.fn(), signUp: jest.fn(), save: jest.fn(), get: jest.fn(), id: 'user123', })), { signUp: jest.fn(), logIn: jest.fn(), become: jest.fn(), current: jest.fn(), } ), Query: jest.fn(() => mockQuery), Object: { extend: jest.fn(), }, })); // Mock JWT jest.mock('jsonwebtoken', () => ({ sign: jest.fn(() => 'mock-jwt-token'), verify: jest.fn(() => ({ userId: 'mock-user-id' })), })); // Mock cache jest.mock('../../../src/config/redis', () => ({ cache: { get: jest.fn(), set: jest.fn(), del: jest.fn(), exists: jest.fn(), }, })); // Mock logger jest.mock('../../../src/utils/logger', () => ({ logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn(), }, })); // Mock errors from error-handler jest.mock('../../../src/middleware/error-handler', () => ({ ApiError: class extends Error { constructor(message: string, public statusCode: number) { super(message); } }, errors: { badRequest: jest.fn((message: string) => new Error(message)), unauthorized: jest.fn((message: string) => new Error(message)), forbidden: jest.fn((message: string) => new Error(message)), notFound: jest.fn((message: string) => new Error(message)), conflict: jest.fn((message: string) => new Error(message)), tooManyRequests: jest.fn((message: string) => new Error(message)), internal: jest.fn((message: string) => new Error(message)), serviceUnavailable: jest.fn((message: string) => new Error(message)), }, })); // Mock authService - 定义在 jest.mock 内部以避免提升问题 jest.mock('../../../src/services/auth/auth.service', () => ({ authService: { generateAccessToken: jest.fn(() => 'mock-access-token'), generateRefreshToken: jest.fn(() => 'mock-refresh-token'), refreshAccessToken: jest.fn(), verifyAccessToken: jest.fn(), verifyRefreshToken: jest.fn(), }, })); describe('UserService 单元测试', () => { let userService: UserService; const mockParse = require('parse/node'); const mockCache = require('../../../src/config/redis').cache; beforeEach(() => { userService = new UserService(); jest.clearAllMocks(); }); describe('register', () => { it('应该成功注册新用户', async () => { const userData = { username: 'testuser', email: 'test@example.com', password: 'Test123456!', }; // Mock Parse.User constructor and signUp instance method const mockUserInstance = { id: 'user123', get: jest.fn((key) => { if (key === 'username') return 'testuser'; if (key === 'email') return 'test@example.com'; return null; }), set: jest.fn(), signUp: jest.fn().mockResolvedValue(undefined), toJSON: jest.fn(() => ({ objectId: 'user123', username: 'testuser', email: 'test@example.com', })), }; // Mock Parse.User constructor to return our mock instance mockParse.User.mockImplementation(() => mockUserInstance); mockQuery.first.mockResolvedValue(null); // No existing user const result = await userService.register(userData); expect(mockUserInstance.signUp).toHaveBeenCalled(); expect(result).toHaveProperty('user'); expect(result).toHaveProperty('token'); expect(result.user.username).toBe('testuser'); }); it('应该处理注册失败的情况', async () => { const userData = { username: 'existinguser', email: 'existing@example.com', password: 'Test123456!', }; // Mock existing user mockQuery.first.mockResolvedValue({ id: 'existing-user' }); await expect(userService.register(userData)).rejects.toThrow(); }); }); describe('login', () => { it('应该成功登录用户', async () => { const loginData = { username: 'testuser', password: 'Test123456!', }; // Mock Parse.User.logIn const mockUser = { id: 'user123', get: jest.fn((key) => { if (key === 'username') return 'testuser'; if (key === 'email') return 'test@example.com'; if (key === 'role') return 'buyer'; return null; }), toJSON: jest.fn(() => ({ objectId: 'user123', username: 'testuser', email: 'test@example.com', role: 'buyer', })), }; mockParse.User.logIn.mockResolvedValue(mockUser); const result = await userService.login(loginData); expect(mockParse.User.logIn).toHaveBeenCalledWith( loginData.username, loginData.password ); expect(result).toHaveProperty('user'); expect(result).toHaveProperty('token'); expect(result).toHaveProperty('refreshToken'); expect(result.user.username).toBe('testuser'); }); it('应该处理登录失败的情况', async () => { const loginData = { username: 'testuser', password: 'wrongpassword', }; mockParse.User.logIn.mockRejectedValue(new Error('用户名或密码错误')); await expect(userService.login(loginData)).rejects.toThrow('用户名或密码错误'); }); }); describe('手机验证码登录', () => { it('应该成功通过手机验证码登录', async () => { const loginData = { phone: '13800138000', code: '123456' }; const mockUser = { id: 'user123', get: jest.fn((key) => { if (key === 'status') return 'active'; if (key === 'username') return 'testuser'; if (key === 'phone') return '13800138000'; if (key === 'email') return 'test@example.com'; if (key === 'role') return 'buyer'; return null; }), toJSON: jest.fn().mockReturnValue({ id: 'user123', username: 'testuser', phone: '13800138000', email: 'test@example.com', role: 'buyer' }) }; // Mock verification code data exists and is valid const codeData = { code: '123456', createdAt: Date.now() - 10000, // 10秒前创建 attempts: 0, type: 'login', }; mockCache.get.mockResolvedValue(codeData); mockCache.del.mockResolvedValue(true); mockQuery.first.mockResolvedValue(mockUser); mockCache.set.mockResolvedValue(true); const result = await userService.loginWithPhoneCode(loginData); expect(mockCache.set).toHaveBeenCalledWith('user:user123', expect.any(Object), 3600); expect(result).toHaveProperty('user'); expect(result).toHaveProperty('token'); expect(result).toHaveProperty('refreshToken'); }); it('应该拒绝无效的验证码', async () => { const loginData = { phone: '13800138000', code: '123456' }; // Mock verification code data exists but is invalid const codeData = { code: '654321', // Different code createdAt: Date.now() - 10000, // 10秒前创建 attempts: 0, type: 'login', }; mockCache.get.mockResolvedValue(codeData); mockCache.set.mockResolvedValue(true); await expect(userService.loginWithPhoneCode(loginData)).rejects.toThrow('验证码错误'); }); it('应该拒绝不存在的手机号', async () => { const loginData = { phone: '13800138000', code: '123456' }; // Mock verification code data exists and is valid const codeData = { code: '123456', createdAt: Date.now() - 10000, // 10秒前创建 attempts: 0, type: 'login', }; mockCache.get.mockResolvedValue(codeData); mockCache.del.mockResolvedValue(true); // Mock user not found mockQuery.first.mockResolvedValue(null); await expect(userService.loginWithPhoneCode(loginData)).rejects.toThrow('手机号未注册'); }); }); describe('refreshToken', () => { it('应该成功刷新Token', async () => { const refreshToken = 'valid-refresh-token'; // 获取 mock 的 authService const { authService } = require('../../../src/services/auth/auth.service'); // Mock authService.refreshAccessToken to return success authService.refreshAccessToken.mockResolvedValue({ token: 'new-access-token', refreshToken: 'new-refresh-token', expiresIn: 86400, }); const result = await userService.refreshToken(refreshToken); expect(authService.refreshAccessToken).toHaveBeenCalledWith(refreshToken); expect(result).toHaveProperty('token'); expect(result).toHaveProperty('refreshToken'); expect(result).toHaveProperty('expiresIn'); }); it('应该拒绝无效的refreshToken', async () => { const refreshToken = 'invalid-refresh-token'; // 获取 mock 的 authService const { authService } = require('../../../src/services/auth/auth.service'); // Mock authService.refreshAccessToken to throw error authService.refreshAccessToken.mockRejectedValue(new Error('Invalid token')); await expect(userService.refreshToken(refreshToken)).rejects.toThrow(); }); }); });