user.service.test.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import { UserService } from '../../../src/services/user/user.service';
  2. // Mock Parse SDK
  3. const mockQuery = {
  4. equalTo: jest.fn().mockReturnThis(),
  5. first: jest.fn(),
  6. find: jest.fn(),
  7. count: jest.fn(),
  8. get: jest.fn(),
  9. };
  10. // Mock Parse SDK
  11. jest.mock('parse/node', () => ({
  12. User: Object.assign(
  13. jest.fn().mockImplementation(() => ({
  14. set: jest.fn(),
  15. signUp: jest.fn(),
  16. save: jest.fn(),
  17. get: jest.fn(),
  18. id: 'user123',
  19. })),
  20. {
  21. signUp: jest.fn(),
  22. logIn: jest.fn(),
  23. become: jest.fn(),
  24. current: jest.fn(),
  25. }
  26. ),
  27. Query: jest.fn(() => mockQuery),
  28. Object: {
  29. extend: jest.fn(),
  30. },
  31. }));
  32. // Mock JWT
  33. jest.mock('jsonwebtoken', () => ({
  34. sign: jest.fn(() => 'mock-jwt-token'),
  35. verify: jest.fn(() => ({ userId: 'mock-user-id' })),
  36. }));
  37. // Mock cache
  38. jest.mock('../../../src/config/redis', () => ({
  39. cache: {
  40. get: jest.fn(),
  41. set: jest.fn(),
  42. del: jest.fn(),
  43. exists: jest.fn(),
  44. },
  45. }));
  46. // Mock logger
  47. jest.mock('../../../src/utils/logger', () => ({
  48. logger: {
  49. info: jest.fn(),
  50. error: jest.fn(),
  51. warn: jest.fn(),
  52. },
  53. }));
  54. // Mock errors from error-handler
  55. jest.mock('../../../src/middleware/error-handler', () => ({
  56. ApiError: class extends Error {
  57. constructor(message: string, public statusCode: number) {
  58. super(message);
  59. }
  60. },
  61. errors: {
  62. badRequest: jest.fn((message: string) => new Error(message)),
  63. unauthorized: jest.fn((message: string) => new Error(message)),
  64. forbidden: jest.fn((message: string) => new Error(message)),
  65. notFound: jest.fn((message: string) => new Error(message)),
  66. conflict: jest.fn((message: string) => new Error(message)),
  67. tooManyRequests: jest.fn((message: string) => new Error(message)),
  68. internal: jest.fn((message: string) => new Error(message)),
  69. serviceUnavailable: jest.fn((message: string) => new Error(message)),
  70. },
  71. }));
  72. // Mock authService - 定义在 jest.mock 内部以避免提升问题
  73. jest.mock('../../../src/services/auth/auth.service', () => ({
  74. authService: {
  75. generateAccessToken: jest.fn(() => 'mock-access-token'),
  76. generateRefreshToken: jest.fn(() => 'mock-refresh-token'),
  77. refreshAccessToken: jest.fn(),
  78. verifyAccessToken: jest.fn(),
  79. verifyRefreshToken: jest.fn(),
  80. },
  81. }));
  82. describe('UserService 单元测试', () => {
  83. let userService: UserService;
  84. const mockParse = require('parse/node');
  85. const mockCache = require('../../../src/config/redis').cache;
  86. beforeEach(() => {
  87. userService = new UserService();
  88. jest.clearAllMocks();
  89. });
  90. describe('register', () => {
  91. it('应该成功注册新用户', async () => {
  92. const userData = {
  93. username: 'testuser',
  94. email: 'test@example.com',
  95. password: 'Test123456!',
  96. };
  97. // Mock Parse.User constructor and signUp instance method
  98. const mockUserInstance = {
  99. id: 'user123',
  100. get: jest.fn((key) => {
  101. if (key === 'username') return 'testuser';
  102. if (key === 'email') return 'test@example.com';
  103. return null;
  104. }),
  105. set: jest.fn(),
  106. signUp: jest.fn().mockResolvedValue(undefined),
  107. toJSON: jest.fn(() => ({
  108. objectId: 'user123',
  109. username: 'testuser',
  110. email: 'test@example.com',
  111. })),
  112. };
  113. // Mock Parse.User constructor to return our mock instance
  114. mockParse.User.mockImplementation(() => mockUserInstance);
  115. mockQuery.first.mockResolvedValue(null); // No existing user
  116. const result = await userService.register(userData);
  117. expect(mockUserInstance.signUp).toHaveBeenCalled();
  118. expect(result).toHaveProperty('user');
  119. expect(result).toHaveProperty('token');
  120. expect(result.user.username).toBe('testuser');
  121. });
  122. it('应该处理注册失败的情况', async () => {
  123. const userData = {
  124. username: 'existinguser',
  125. email: 'existing@example.com',
  126. password: 'Test123456!',
  127. };
  128. // Mock existing user
  129. mockQuery.first.mockResolvedValue({ id: 'existing-user' });
  130. await expect(userService.register(userData)).rejects.toThrow();
  131. });
  132. });
  133. describe('login', () => {
  134. it('应该成功登录用户', async () => {
  135. const loginData = {
  136. username: 'testuser',
  137. password: 'Test123456!',
  138. };
  139. // Mock Parse.User.logIn
  140. const mockUser = {
  141. id: 'user123',
  142. get: jest.fn((key) => {
  143. if (key === 'username') return 'testuser';
  144. if (key === 'email') return 'test@example.com';
  145. if (key === 'role') return 'buyer';
  146. return null;
  147. }),
  148. toJSON: jest.fn(() => ({
  149. objectId: 'user123',
  150. username: 'testuser',
  151. email: 'test@example.com',
  152. role: 'buyer',
  153. })),
  154. };
  155. mockParse.User.logIn.mockResolvedValue(mockUser);
  156. const result = await userService.login(loginData);
  157. expect(mockParse.User.logIn).toHaveBeenCalledWith(
  158. loginData.username,
  159. loginData.password
  160. );
  161. expect(result).toHaveProperty('user');
  162. expect(result).toHaveProperty('token');
  163. expect(result).toHaveProperty('refreshToken');
  164. expect(result.user.username).toBe('testuser');
  165. });
  166. it('应该处理登录失败的情况', async () => {
  167. const loginData = {
  168. username: 'testuser',
  169. password: 'wrongpassword',
  170. };
  171. mockParse.User.logIn.mockRejectedValue(new Error('用户名或密码错误'));
  172. await expect(userService.login(loginData)).rejects.toThrow('用户名或密码错误');
  173. });
  174. });
  175. describe('手机验证码登录', () => {
  176. it('应该成功通过手机验证码登录', async () => {
  177. const loginData = { phone: '13800138000', code: '123456' };
  178. const mockUser = {
  179. id: 'user123',
  180. get: jest.fn((key) => {
  181. if (key === 'status') return 'active';
  182. if (key === 'username') return 'testuser';
  183. if (key === 'phone') return '13800138000';
  184. if (key === 'email') return 'test@example.com';
  185. if (key === 'role') return 'buyer';
  186. return null;
  187. }),
  188. toJSON: jest.fn().mockReturnValue({
  189. id: 'user123',
  190. username: 'testuser',
  191. phone: '13800138000',
  192. email: 'test@example.com',
  193. role: 'buyer'
  194. })
  195. };
  196. // Mock verification code data exists and is valid
  197. const codeData = {
  198. code: '123456',
  199. createdAt: Date.now() - 10000, // 10秒前创建
  200. attempts: 0,
  201. type: 'login',
  202. };
  203. mockCache.get.mockResolvedValue(codeData);
  204. mockCache.del.mockResolvedValue(true);
  205. mockQuery.first.mockResolvedValue(mockUser);
  206. mockCache.set.mockResolvedValue(true);
  207. const result = await userService.loginWithPhoneCode(loginData);
  208. expect(mockCache.set).toHaveBeenCalledWith('user:user123', expect.any(Object), 3600);
  209. expect(result).toHaveProperty('user');
  210. expect(result).toHaveProperty('token');
  211. expect(result).toHaveProperty('refreshToken');
  212. });
  213. it('应该拒绝无效的验证码', async () => {
  214. const loginData = { phone: '13800138000', code: '123456' };
  215. // Mock verification code data exists but is invalid
  216. const codeData = {
  217. code: '654321', // Different code
  218. createdAt: Date.now() - 10000, // 10秒前创建
  219. attempts: 0,
  220. type: 'login',
  221. };
  222. mockCache.get.mockResolvedValue(codeData);
  223. mockCache.set.mockResolvedValue(true);
  224. await expect(userService.loginWithPhoneCode(loginData)).rejects.toThrow('验证码错误');
  225. });
  226. it('应该拒绝不存在的手机号', async () => {
  227. const loginData = { phone: '13800138000', code: '123456' };
  228. // Mock verification code data exists and is valid
  229. const codeData = {
  230. code: '123456',
  231. createdAt: Date.now() - 10000, // 10秒前创建
  232. attempts: 0,
  233. type: 'login',
  234. };
  235. mockCache.get.mockResolvedValue(codeData);
  236. mockCache.del.mockResolvedValue(true);
  237. // Mock user not found
  238. mockQuery.first.mockResolvedValue(null);
  239. await expect(userService.loginWithPhoneCode(loginData)).rejects.toThrow('手机号未注册');
  240. });
  241. });
  242. describe('refreshToken', () => {
  243. it('应该成功刷新Token', async () => {
  244. const refreshToken = 'valid-refresh-token';
  245. // 获取 mock 的 authService
  246. const { authService } = require('../../../src/services/auth/auth.service');
  247. // Mock authService.refreshAccessToken to return success
  248. authService.refreshAccessToken.mockResolvedValue({
  249. token: 'new-access-token',
  250. refreshToken: 'new-refresh-token',
  251. expiresIn: 86400,
  252. });
  253. const result = await userService.refreshToken(refreshToken);
  254. expect(authService.refreshAccessToken).toHaveBeenCalledWith(refreshToken);
  255. expect(result).toHaveProperty('token');
  256. expect(result).toHaveProperty('refreshToken');
  257. expect(result).toHaveProperty('expiresIn');
  258. });
  259. it('应该拒绝无效的refreshToken', async () => {
  260. const refreshToken = 'invalid-refresh-token';
  261. // 获取 mock 的 authService
  262. const { authService } = require('../../../src/services/auth/auth.service');
  263. // Mock authService.refreshAccessToken to throw error
  264. authService.refreshAccessToken.mockRejectedValue(new Error('Invalid token'));
  265. await expect(userService.refreshToken(refreshToken)).rejects.toThrow();
  266. });
  267. });
  268. });