auth.test.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import { Request, Response, NextFunction } from 'express';
  2. // 创建自定义JWT错误类
  3. class MockTokenExpiredError extends Error {
  4. name = 'TokenExpiredError';
  5. expiredAt: Date;
  6. constructor(message: string, expiredAt: Date) {
  7. super(message);
  8. this.expiredAt = expiredAt;
  9. }
  10. }
  11. class MockJsonWebTokenError extends Error {
  12. name = 'JsonWebTokenError';
  13. constructor(message: string) {
  14. super(message);
  15. }
  16. }
  17. // 创建自定义ApiError类
  18. class MockApiError extends Error {
  19. constructor(
  20. public statusCode: number,
  21. public message: string,
  22. public code?: string,
  23. public details?: any
  24. ) {
  25. super(message);
  26. this.name = 'ApiError';
  27. }
  28. }
  29. // Mock JWT
  30. jest.mock('jsonwebtoken', () => ({
  31. verify: jest.fn(),
  32. TokenExpiredError: MockTokenExpiredError,
  33. JsonWebTokenError: MockJsonWebTokenError,
  34. }));
  35. // Mock Parse
  36. jest.mock('parse/node', () => ({
  37. Parse: {
  38. Query: jest.fn().mockImplementation(() => ({
  39. equalTo: jest.fn().mockReturnThis(),
  40. first: jest.fn(),
  41. })),
  42. User: {
  43. current: jest.fn(),
  44. },
  45. },
  46. }));
  47. // Mock logger
  48. jest.mock('../../../src/utils/logger', () => ({
  49. logger: {
  50. info: jest.fn(),
  51. error: jest.fn(),
  52. warn: jest.fn(),
  53. },
  54. }));
  55. // Mock cache
  56. jest.mock('../../../src/config/redis', () => ({
  57. cache: {
  58. get: jest.fn(),
  59. set: jest.fn(),
  60. del: jest.fn(),
  61. },
  62. }));
  63. // Mock error handler
  64. jest.mock('../../../src/middleware/error-handler', () => ({
  65. errors: {
  66. unauthorized: (message: string) => new MockApiError(401, message, 'UNAUTHORIZED'),
  67. forbidden: (message: string) => new MockApiError(403, message, 'FORBIDDEN'),
  68. },
  69. }));
  70. import { authenticate } from '../../../src/middleware/auth';
  71. describe('Auth Middleware 单元测试', () => {
  72. let mockRequest: Partial<Request>;
  73. let mockResponse: Partial<Response>;
  74. let nextFunction: NextFunction;
  75. const mockParse = require('parse/node');
  76. const mockJwt = require('jsonwebtoken');
  77. beforeEach(() => {
  78. mockRequest = {
  79. headers: {},
  80. };
  81. mockResponse = {
  82. status: jest.fn().mockReturnThis(),
  83. json: jest.fn(),
  84. };
  85. nextFunction = jest.fn();
  86. jest.clearAllMocks();
  87. });
  88. describe('认证中间件', () => {
  89. it('应该允许有效的JWT token通过', async () => {
  90. const mockUser = {
  91. id: 'user123',
  92. username: 'testuser',
  93. email: 'test@example.com',
  94. objectId: 'user123',
  95. };
  96. const mockToken = 'valid.jwt.token';
  97. const mockDecodedToken = {
  98. userId: 'user123',
  99. username: 'testuser',
  100. iat: Date.now() / 1000,
  101. exp: Date.now() / 1000 + 3600,
  102. };
  103. mockRequest.headers = {
  104. authorization: `Bearer ${mockToken}`,
  105. };
  106. mockJwt.verify.mockReturnValue(mockDecodedToken);
  107. mockParse.Parse.Query().first.mockResolvedValue(mockUser);
  108. await authenticate(
  109. mockRequest as Request,
  110. mockResponse as Response,
  111. nextFunction
  112. );
  113. expect(mockJwt.verify).toHaveBeenCalledWith(
  114. mockToken,
  115. process.env.JWT_SECRET
  116. );
  117. expect(mockRequest.user).toEqual(mockDecodedToken);
  118. expect(nextFunction).toHaveBeenCalledWith();
  119. });
  120. it('应该拒绝没有Authorization头的请求', async () => {
  121. await authenticate(
  122. mockRequest as Request,
  123. mockResponse as Response,
  124. nextFunction
  125. );
  126. expect(nextFunction).toHaveBeenCalledWith(
  127. expect.objectContaining({
  128. statusCode: 401,
  129. message: '未提供访问令牌'
  130. })
  131. );
  132. });
  133. it('应该拒绝无效的JWT token', async () => {
  134. const mockToken = 'invalid.jwt.token';
  135. mockRequest.headers = {
  136. authorization: `Bearer ${mockToken}`,
  137. };
  138. mockJwt.verify.mockImplementation(() => {
  139. throw new MockJsonWebTokenError('Invalid token');
  140. });
  141. await authenticate(
  142. mockRequest as Request,
  143. mockResponse as Response,
  144. nextFunction
  145. );
  146. expect(mockJwt.verify).toHaveBeenCalledWith(
  147. mockToken,
  148. process.env.JWT_SECRET
  149. );
  150. expect(nextFunction).toHaveBeenCalledWith(
  151. expect.objectContaining({
  152. statusCode: 401,
  153. message: '无效的访问令牌'
  154. })
  155. );
  156. });
  157. it('应该拒绝过期的JWT token', async () => {
  158. const mockToken = 'expired.jwt.token';
  159. mockRequest.headers = {
  160. authorization: `Bearer ${mockToken}`,
  161. };
  162. mockJwt.verify.mockImplementation(() => {
  163. throw new MockTokenExpiredError('Token expired', new Date());
  164. });
  165. await authenticate(
  166. mockRequest as Request,
  167. mockResponse as Response,
  168. nextFunction
  169. );
  170. expect(mockJwt.verify).toHaveBeenCalledWith(
  171. mockToken,
  172. process.env.JWT_SECRET
  173. );
  174. expect(nextFunction).toHaveBeenCalledWith(
  175. expect.objectContaining({
  176. statusCode: 401,
  177. message: '访问令牌已过期'
  178. })
  179. );
  180. });
  181. it('应该拒绝数据库中不存在的用户', async () => {
  182. const mockToken = 'valid.jwt.token';
  183. const mockDecodedToken = {
  184. userId: 'nonexistent_user',
  185. username: 'testuser',
  186. iat: Date.now() / 1000,
  187. exp: Date.now() / 1000 + 3600,
  188. };
  189. mockRequest.headers = {
  190. authorization: `Bearer ${mockToken}`,
  191. };
  192. mockJwt.verify.mockReturnValue(mockDecodedToken);
  193. mockParse.Parse.Query().first.mockResolvedValue(null);
  194. await authenticate(
  195. mockRequest as Request,
  196. mockResponse as Response,
  197. nextFunction
  198. );
  199. expect(mockJwt.verify).toHaveBeenCalledWith(
  200. mockToken,
  201. process.env.JWT_SECRET
  202. );
  203. expect(nextFunction).toHaveBeenCalledWith();
  204. });
  205. });
  206. });