65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { createAdminRateLimiter, FORCE_UNLOCK_LIMIT } from './admin-rate-limit.js';
|
|
|
|
describe('AdminRateLimiter', () => {
|
|
it('allows up to maxRequests within the window', () => {
|
|
const limiter = createAdminRateLimiter({ windowMs: 60_000, maxRequests: 3 });
|
|
const now = new Date('2026-05-12T00:00:00Z');
|
|
expect(limiter.check('admin-1', now).allowed).toBe(true);
|
|
expect(limiter.check('admin-1', now).allowed).toBe(true);
|
|
expect(limiter.check('admin-1', now).allowed).toBe(true);
|
|
});
|
|
|
|
it('denies the (maxRequests+1)th request and returns retryAfterSeconds', () => {
|
|
const limiter = createAdminRateLimiter({ windowMs: 60_000, maxRequests: 2 });
|
|
const now = new Date('2026-05-12T00:00:00Z');
|
|
limiter.check('admin-1', now);
|
|
limiter.check('admin-1', now);
|
|
const denied = limiter.check('admin-1', now);
|
|
expect(denied.allowed).toBe(false);
|
|
expect(denied.retryAfterSeconds).toBeGreaterThan(0);
|
|
expect(denied.retryAfterSeconds).toBeLessThanOrEqual(60);
|
|
});
|
|
|
|
it('isolates buckets between users', () => {
|
|
const limiter = createAdminRateLimiter({ windowMs: 60_000, maxRequests: 1 });
|
|
const now = new Date('2026-05-12T00:00:00Z');
|
|
expect(limiter.check('alice', now).allowed).toBe(true);
|
|
expect(limiter.check('bob', now).allowed).toBe(true);
|
|
expect(limiter.check('alice', now).allowed).toBe(false);
|
|
});
|
|
|
|
it('starts a new window after windowMs elapses', () => {
|
|
const limiter = createAdminRateLimiter({ windowMs: 1000, maxRequests: 1 });
|
|
const t0 = new Date('2026-05-12T00:00:00Z');
|
|
const t1 = new Date('2026-05-12T00:00:01.500Z');
|
|
expect(limiter.check('admin-1', t0).allowed).toBe(true);
|
|
expect(limiter.check('admin-1', t0).allowed).toBe(false);
|
|
expect(limiter.check('admin-1', t1).allowed).toBe(true);
|
|
});
|
|
|
|
it('reset(userId) clears one bucket', () => {
|
|
const limiter = createAdminRateLimiter({ windowMs: 60_000, maxRequests: 1 });
|
|
const now = new Date('2026-05-12T00:00:00Z');
|
|
limiter.check('admin-1', now);
|
|
expect(limiter.check('admin-1', now).allowed).toBe(false);
|
|
limiter.reset('admin-1');
|
|
expect(limiter.check('admin-1', now).allowed).toBe(true);
|
|
});
|
|
|
|
it('resetAll() clears all buckets', () => {
|
|
const limiter = createAdminRateLimiter({ windowMs: 60_000, maxRequests: 1 });
|
|
const now = new Date('2026-05-12T00:00:00Z');
|
|
limiter.check('alice', now);
|
|
limiter.check('bob', now);
|
|
limiter.resetAll();
|
|
expect(limiter.check('alice', now).allowed).toBe(true);
|
|
expect(limiter.check('bob', now).allowed).toBe(true);
|
|
});
|
|
|
|
it('FORCE_UNLOCK_LIMIT default is 10/hr', () => {
|
|
expect(FORCE_UNLOCK_LIMIT.maxRequests).toBe(10);
|
|
expect(FORCE_UNLOCK_LIMIT.windowMs).toBe(60 * 60 * 1000);
|
|
});
|
|
});
|