Files
sonum/server/rateLimit.test.ts

34 lines
1.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { getClientIp, SlidingWindowRateLimiter, type RateLimitRule } from "./rateLimit";
const shortRule: RateLimitRule = {
scope: "test",
maxAttempts: 2,
windowMs: 1_000,
};
describe("SlidingWindowRateLimiter", () => {
it("bloque après la limite et autorise à nouveau une fois la fenêtre expirée", () => {
const limiter = new SlidingWindowRateLimiter();
expect(limiter.consume(shortRule, "user-1", 1_000)).toMatchObject({ allowed: true, remaining: 1 });
expect(limiter.consume(shortRule, "user-1", 1_100)).toMatchObject({ allowed: true, remaining: 0 });
expect(limiter.consume(shortRule, "user-1", 1_200)).toMatchObject({ allowed: false, retryAfterMs: 800 });
// À 2 001 ms, la tentative de 1 100 ms est encore dans la fenêtre glissante.
expect(limiter.consume(shortRule, "user-1", 2_001)).toMatchObject({ allowed: true, remaining: 0 });
});
it("isole les compteurs de chaque clé", () => {
const limiter = new SlidingWindowRateLimiter();
limiter.consume(shortRule, "user-1", 1_000);
limiter.consume(shortRule, "user-1", 1_100);
expect(limiter.consume(shortRule, "user-2", 1_200)).toMatchObject({ allowed: true, remaining: 1 });
});
it("extrait uniquement la première adresse client transmise par le proxy", () => {
expect(getClientIp({ "x-forwarded-for": "203.0.113.8, 10.0.0.1" })).toBe("203.0.113.8");
expect(getClientIp({})).toBe("unknown-client");
});
});