rate-limiter.test.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { Request, Response, NextFunction } from 'express';
  2. import { rateLimiter, authLimiter, orderLimiter } from '../../../src/middleware/rate-limiter';
  3. // Mock express-rate-limit模块
  4. jest.mock('express-rate-limit', () => {
  5. return jest.fn().mockImplementation((options) => {
  6. return jest.fn().mockImplementation((req: Request, res: Response, next: NextFunction) => {
  7. // 模拟限流逻辑
  8. const count = (req as any).rateLimitCount ? (req as any).rateLimitCount + 1 : 1;
  9. (req as any).rateLimitCount = count;
  10. if (count > options.max) {
  11. // 如果有自定义处理器,使用它
  12. if (options.handler) {
  13. return options.handler(req, res, next);
  14. }
  15. // 否则使用默认的message
  16. return res.status(429).json(options.message);
  17. }
  18. return next();
  19. });
  20. });
  21. });
  22. describe('Rate Limit Middleware 单元测试', () => {
  23. let mockRequest: Partial<Request>;
  24. let mockResponse: Partial<Response>;
  25. let nextFunction: jest.Mock;
  26. beforeEach(() => {
  27. // 重置所有模拟
  28. jest.clearAllMocks();
  29. // 创建模拟请求对象
  30. mockRequest = {
  31. ip: '127.0.0.1',
  32. path: '/api/test',
  33. method: 'GET',
  34. app: {} as any
  35. };
  36. // 创建模拟响应对象
  37. mockResponse = {
  38. status: jest.fn().mockReturnThis(),
  39. json: jest.fn().mockReturnThis(),
  40. };
  41. // 创建模拟next函数
  42. nextFunction = jest.fn();
  43. });
  44. describe('通用限流中间件', () => {
  45. it('应该在请求限制内允许通过', () => {
  46. // 调用限流中间件
  47. rateLimiter(mockRequest as Request, mockResponse as Response, nextFunction);
  48. // 验证next被调用
  49. expect(nextFunction).toHaveBeenCalledTimes(1);
  50. expect(mockResponse.status).not.toHaveBeenCalled();
  51. });
  52. it('应该在超过限制时返回429状态码', () => {
  53. // 超过限制的请求
  54. for (let i = 0; i < 101; i++) {
  55. rateLimiter(mockRequest as Request, mockResponse as Response, nextFunction);
  56. }
  57. // 验证响应
  58. expect(nextFunction).toHaveBeenCalledTimes(100);
  59. expect(mockResponse.status).toHaveBeenCalledWith(429);
  60. expect(mockResponse.json).toHaveBeenCalledWith({
  61. code: 429,
  62. error: 'RATE_LIMIT_EXCEEDED',
  63. message: '请求过于频繁,请稍后再试',
  64. timestamp: expect.any(Number),
  65. });
  66. });
  67. });
  68. describe('登录限流中间件', () => {
  69. it('应该在登录尝试限制内允许通过', () => {
  70. // 调用登录限流中间件
  71. authLimiter(mockRequest as Request, mockResponse as Response, nextFunction);
  72. // 验证next被调用
  73. expect(nextFunction).toHaveBeenCalledTimes(1);
  74. expect(mockResponse.status).not.toHaveBeenCalled();
  75. });
  76. it('应该在超过登录尝试限制时返回429状态码', () => {
  77. // 超过限制的登录尝试
  78. for (let i = 0; i < 6; i++) {
  79. authLimiter(mockRequest as Request, mockResponse as Response, nextFunction);
  80. }
  81. // 验证响应
  82. expect(nextFunction).toHaveBeenCalledTimes(5);
  83. expect(mockResponse.status).toHaveBeenCalledWith(429);
  84. expect(mockResponse.json).toHaveBeenCalledWith({
  85. code: 429,
  86. error: 'RATE_LIMIT_EXCEEDED',
  87. message: '登录尝试次数过多,请1小时后再试',
  88. timestamp: expect.any(Number),
  89. });
  90. });
  91. });
  92. describe('创建订单限流中间件', () => {
  93. it('应该在订单创建限制内允许通过', () => {
  94. // 模拟已登录用户
  95. (mockRequest as any).user = { id: 'user123' };
  96. // 调用订单限流中间件
  97. orderLimiter(mockRequest as Request, mockResponse as Response, nextFunction);
  98. // 验证next被调用
  99. expect(nextFunction).toHaveBeenCalledTimes(1);
  100. expect(mockResponse.status).not.toHaveBeenCalled();
  101. });
  102. it('应该在超过订单创建限制时返回429状态码', () => {
  103. // 模拟已登录用户
  104. (mockRequest as any).user = { id: 'user123' };
  105. // 超过限制的订单创建请求
  106. for (let i = 0; i < 11; i++) {
  107. orderLimiter(mockRequest as Request, mockResponse as Response, nextFunction);
  108. }
  109. // 验证响应
  110. expect(nextFunction).toHaveBeenCalledTimes(10);
  111. expect(mockResponse.status).toHaveBeenCalledWith(429);
  112. expect(mockResponse.json).toHaveBeenCalledWith({
  113. code: 429,
  114. error: 'RATE_LIMIT_EXCEEDED',
  115. message: '下单过于频繁,请稍后再试',
  116. timestamp: expect.any(Number),
  117. });
  118. });
  119. it('应该对未登录用户使用IP作为限流键', () => {
  120. // 不设置用户信息,模拟未登录用户
  121. delete (mockRequest as any).user;
  122. // 调用订单限流中间件
  123. orderLimiter(mockRequest as Request, mockResponse as Response, nextFunction);
  124. // 验证next被调用
  125. expect(nextFunction).toHaveBeenCalledTimes(1);
  126. });
  127. });
  128. });