import assert from 'node:assert/strict'; import { test } from 'node:test'; import { LoginRateLimiter } from '../src/auth/login-rate-limiter'; import { ApiException } from '../src/common/api-exception'; test('登录分别限制 IP 与可信身份,窗口过期后恢复且返回重试期限', (context) => { context.mock.method(Date, 'now', () => 1000); const limiter = new LoginRateLimiter(); for (let i = 0; i < 30; i++) limiter.checkIp('127.0.0.1'); assert.throws( () => limiter.checkIp('127.0.0.1'), (error: unknown) => error instanceof ApiException && error.reason === 'RATE_LIMITED' && error.retryAfterSeconds === 60, ); limiter.checkIp('127.0.0.2'); for (let i = 0; i < 10; i++) limiter.checkIdentity('openid-a'); assert.throws(() => limiter.checkIdentity('openid-a'), ApiException); limiter.checkIdentity('openid-b'); context.mock.method(Date, 'now', () => 61_000); limiter.checkIp('127.0.0.1'); limiter.checkIdentity('openid-a'); }); test('大量来源不会无限增长内存,过期后可以回收容量', (context) => { context.mock.method(Date, 'now', () => 0); const limiter = new LoginRateLimiter(); for (let i = 0; i < 10_000; i++) limiter.checkIp(`ip-${i}`); assert.throws(() => limiter.checkIp('new-ip'), ApiException); limiter.checkIp('ip-0'); context.mock.method(Date, 'now', () => 60_000); limiter.checkIp('new-ip'); });