报告工人的等待到点就摘掉取消监听。本地数据库缓存表结构,并把设置身份合成一条语句,对照通过后默认打开,写成 off 仍走原来的语句。交付、重算和限流的内存表会过期。登录读缓存默认关闭,要开再设 IDENTITY_SESSION_COOKIE_CACHE=on。 自测:tsc 通过,lint 0 error,点名测试 134 通过,test:db 开关关和开都只有已知的 13 条失败,往返 49 降到 32。 Co-Authored-By: Grok (agent loop)
113 lines
2.7 KiB
TypeScript
113 lines
2.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import {
|
|
consumeRequestRateLimit,
|
|
resetRequestRateLimitMaintenanceForTests,
|
|
} from "../src/lib/request-rate-limit.ts";
|
|
|
|
test("a user is allowed up to the window limit and then must wait", () => {
|
|
const store = new Map<string, number[]>();
|
|
const first = consumeRequestRateLimit({
|
|
key: "search:user-1",
|
|
limit: 2,
|
|
windowMs: 60_000,
|
|
now: 1_000,
|
|
store,
|
|
});
|
|
const second = consumeRequestRateLimit({
|
|
key: "search:user-1",
|
|
limit: 2,
|
|
windowMs: 60_000,
|
|
now: 2_000,
|
|
store,
|
|
});
|
|
const blocked = consumeRequestRateLimit({
|
|
key: "search:user-1",
|
|
limit: 2,
|
|
windowMs: 60_000,
|
|
now: 3_000,
|
|
store,
|
|
});
|
|
const otherUser = consumeRequestRateLimit({
|
|
key: "search:user-2",
|
|
limit: 2,
|
|
windowMs: 60_000,
|
|
now: 3_000,
|
|
store,
|
|
});
|
|
const afterWindow = consumeRequestRateLimit({
|
|
key: "search:user-1",
|
|
limit: 2,
|
|
windowMs: 60_000,
|
|
now: 61_500,
|
|
store,
|
|
});
|
|
|
|
assert.deepEqual(first, { ok: true });
|
|
assert.deepEqual(second, { ok: true });
|
|
assert.deepEqual(blocked, { ok: false, retryAfterSeconds: 58 });
|
|
assert.deepEqual(otherUser, { ok: true });
|
|
assert.deepEqual(afterWindow, { ok: true });
|
|
});
|
|
|
|
test("keys past the longest seen window are swept, and a one-hour key survives 61 seconds", () => {
|
|
resetRequestRateLimitMaintenanceForTests();
|
|
const shortWindow = new Map<string, number[]>();
|
|
consumeRequestRateLimit({
|
|
key: "old",
|
|
limit: 5,
|
|
windowMs: 60_000,
|
|
now: 0,
|
|
store: shortWindow,
|
|
});
|
|
for (let index = 0; index < 998; index += 1) {
|
|
consumeRequestRateLimit({
|
|
key: `pad:${index}`,
|
|
limit: 5,
|
|
windowMs: 60_000,
|
|
now: 0,
|
|
store: shortWindow,
|
|
});
|
|
}
|
|
consumeRequestRateLimit({
|
|
key: "trigger",
|
|
limit: 5,
|
|
windowMs: 60_000,
|
|
now: 61_000,
|
|
store: shortWindow,
|
|
});
|
|
assert.equal(shortWindow.has("old"), false);
|
|
assert.equal(shortWindow.get("old"), undefined);
|
|
assert.equal(shortWindow.has("trigger"), true);
|
|
assert.equal(shortWindow.size, 1);
|
|
|
|
resetRequestRateLimitMaintenanceForTests();
|
|
const mixed = new Map<string, number[]>();
|
|
consumeRequestRateLimit({
|
|
key: "synastry:person",
|
|
limit: 8,
|
|
windowMs: 60 * 60_000,
|
|
now: 0,
|
|
store: mixed,
|
|
});
|
|
consumeRequestRateLimit({
|
|
key: "ephemeris:person",
|
|
limit: 30,
|
|
windowMs: 60_000,
|
|
now: 0,
|
|
store: mixed,
|
|
});
|
|
for (let index = 0; index < 998; index += 1) {
|
|
consumeRequestRateLimit({
|
|
key: `later:${index}`,
|
|
limit: 5,
|
|
windowMs: 60_000,
|
|
now: 61_000,
|
|
store: mixed,
|
|
});
|
|
}
|
|
assert.equal(mixed.has("synastry:person"), true);
|
|
assert.equal(mixed.has("ephemeris:person"), true);
|
|
resetRequestRateLimitMaintenanceForTests();
|
|
});
|