2b422ca197
Build & Push / Pipeline Tests (push) Failing after 1m14s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 53s
Test / API Unit Tests (push) Failing after 50s
Test / Homepage Unit Tests (push) Successful in 45s
Test / Carplace Unit Tests (push) Successful in 44s
Test / Admin Unit Tests (push) Successful in 42s
Test / Dashboard Unit Tests (push) Successful in 43s
Test / API Integration Tests (push) Successful in 1m4s
83 lines
3.3 KiB
TypeScript
83 lines
3.3 KiB
TypeScript
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)
|
|
})
|
|
})
|