35 lines
1.4 KiB
TypeScript
35 lines
1.4 KiB
TypeScript
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');
|
|
});
|