maestro/src/ssh/admin-rate-limit.test.ts
2026-06-03 05:08:00 +00:00

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);
});
});