import { beforeEach, describe, expect, it, vi } from 'vitest' const createdLimiters: any[] = [] vi.mock('express-rate-limit', () => ({ default: vi.fn((config: any) => { createdLimiters.push(config) return config }), ipKeyGenerator: vi.fn((ip: string) => `ip:${ip}`), })) vi.mock('../security/tokens', () => ({ verifyAnyActorToken: vi.fn((token: string) => { if (token === 'employee-token') return { type: 'employee', sub: 'employee_1' } if (token === 'renter-token') return { type: 'renter', sub: 'renter_1' } throw new Error('Invalid actor token') }), })) import rateLimit, { ipKeyGenerator } from 'express-rate-limit' describe('rateLimiter middleware configuration', () => { beforeEach(() => { createdLimiters.length = 0 vi.resetModules() }) it('configures auth limiter to count failed attempts only', async () => { const { authLimiter } = await import('./rateLimiter') expect(authLimiter.max).toBe(20) expect(authLimiter.windowMs).toBe(15 * 60 * 1000) expect(authLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true) expect(authLimiter.skip({ method: 'POST' } as any)).toBe(false) expect(authLimiter.skipSuccessfulRequests).toBe(true) expect(authLimiter.message).toMatchObject({ error: 'too_many_requests', statusCode: 429 }) expect(rateLimit).toHaveBeenCalled() }) it('keys general API limits by verified actor identity before falling back to request context', async () => { const { apiLimiter } = await import('./rateLimiter') expect(apiLimiter.keyGenerator({ ip: '203.0.113.10', headers: { cookie: 'theme=dark; employee_session=employee-token' }, companyId: 'company_1', } as any)).toBe('ip:203.0.113.10:employee:employee_1') expect(apiLimiter.keyGenerator({ ip: '203.0.113.10', headers: { authorization: 'Bearer renter-token' }, renterId: 'legacy_renter_context', } as any)).toBe('ip:203.0.113.10:renter:renter_1') expect(apiLimiter.keyGenerator({ ip: '203.0.113.10', headers: { authorization: 'Bearer invalid-token' }, companyId: 'company_1', } as any)).toBe('ip:203.0.113.10:company_1') expect(apiLimiter.keyGenerator({ ip: '203.0.113.10', headers: {}, renterId: 'renter_1' } as any)).toBe('ip:203.0.113.10:renter_1') expect(ipKeyGenerator).toHaveBeenCalledWith('203.0.113.10') }) it('uses tighter public and admin limits with explicit 429 payloads', async () => { const { publicLimiter, adminLimiter } = await import('./rateLimiter') expect(publicLimiter.max).toBe(60) expect(publicLimiter.message.message).toBe('Rate limit exceeded') expect(publicLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true) expect(adminLimiter.max).toBe(100) expect(adminLimiter.message.message).toBe('Too many admin requests') expect(adminLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true) }) it('uses a higher API cap and skips preflight requests before authenticated actor limits', async () => { const { apiLimiter, actorLimiter } = await import('./rateLimiter') expect(apiLimiter.max).toBe(300) expect(apiLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true) expect(apiLimiter.skip({ method: 'GET' } as any)).toBe(false) expect(actorLimiter.skip({ method: 'OPTIONS' } as any)).toBe(true) }) })