Checkpoint: Pagination serveur des établissements et des messages, tri serveur, navigation utilisateur, limitation de débit sur connexion locale, messages et demandes, avec 46 tests Vitest, typage TypeScript et build validés.
This commit is contained in:
33
server/rateLimit.test.ts
Normal file
33
server/rateLimit.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user