| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- 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();
- });
- });
- });
|