import { describe, it, expect } from 'vitest' import { policyForPlan, PRE_AUTH_POLICY, checkPreAuthRate, checkConnectRate, checkConcurrentSessions, checkEnrollRate, } from '../src/ratelimit/quota.js' import { fakeCountingBucket } from './_helpers.js' const NOW = 1_700_000_000 describe('rate-limits / quotas (T11)', () => { it('pre-auth throttle (Finding-5): one IP hammering many subdomains is throttled, no account', async () => { const bucket = fakeCountingBucket() const ip = 'ip-hash-1' let allowed = 0 for (let i = 0; i < PRE_AUTH_POLICY.preAuthPerMinPerIp + 5; i++) { if (await checkPreAuthRate(ip, bucket, NOW)) allowed++ } expect(allowed).toBe(PRE_AUTH_POLICY.preAuthPerMinPerIp) // burst then throttled }) it('pre-auth throttle refills after its window', async () => { const bucket = fakeCountingBucket() const ip = 'ip-hash-2' for (let i = 0; i < PRE_AUTH_POLICY.preAuthPerMinPerIp; i++) await checkPreAuthRate(ip, bucket, NOW) expect(await checkPreAuthRate(ip, bucket, NOW)).toBe(false) expect(await checkPreAuthRate(ip, bucket, NOW + 120)).toBe(true) // refilled }) it('per-account connect rate: over connectPerMin → false, refills after window', async () => { const bucket = fakeCountingBucket() const policy = policyForPlan('free') let allowed = 0 for (let i = 0; i < policy.connectPerMin + 3; i++) { if (await checkConnectRate('acct-A', policy, bucket, NOW)) allowed++ } expect(allowed).toBe(policy.connectPerMin) expect(await checkConnectRate('acct-A', policy, bucket, NOW + 120)).toBe(true) }) it('per-account limits are per accountId — A hitting its limit does not affect B', async () => { const bucket = fakeCountingBucket() const policy = policyForPlan('free') for (let i = 0; i < policy.connectPerMin + 5; i++) await checkConnectRate('acct-A', policy, bucket, NOW) expect(await checkConnectRate('acct-A', policy, bucket, NOW)).toBe(false) expect(await checkConnectRate('acct-B', policy, bucket, NOW)).toBe(true) // B unaffected }) it('cross-key isolation: pre-auth IP throttle and account quota use disjoint namespaces', async () => { const bucket = fakeCountingBucket() const policy = policyForPlan('free') // Exhaust the pre-auth bucket for an IP hash that equals an accountId string. for (let i = 0; i < PRE_AUTH_POLICY.preAuthPerMinPerIp; i++) await checkPreAuthRate('collide', bucket, NOW) expect(await checkPreAuthRate('collide', bucket, NOW)).toBe(false) // The account quota for the same string is a different key → still allowed. expect(await checkConnectRate('collide', policy, bucket, NOW)).toBe(true) }) it('concurrent sessions over the cap → denied', () => { const policy = policyForPlan('free') expect(checkConcurrentSessions('acct-A', policy.maxConcurrentSessions - 1, policy)).toBe(true) expect(checkConcurrentSessions('acct-A', policy.maxConcurrentSessions, policy)).toBe(false) }) it('enroll spam over enrollPerHour → denied', async () => { const bucket = fakeCountingBucket() const policy = policyForPlan('free') let allowed = 0 for (let i = 0; i < policy.enrollPerHour + 2; i++) { if (await checkEnrollRate('acct-A', policy, bucket, NOW)) allowed++ } expect(allowed).toBe(policy.enrollPerHour) }) })