OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note / test / unit / rate-limiter.test.js

87 lines · javascript ref:
import test from 'brittle'
import { RateLimiter } from '../../lib/auth/rate-limiter.js'

test('RateLimiter - allows requests within limit', async (t) => {
  const limiter = new RateLimiter({
    limits: {
      'notes': { tokens: 5, refillRate: 1, refillInterval: 60000 }
    }
  })

  const result = await limiter.check('user1', 'notes')
  t.ok(result.allowed, 'first request allowed')
  t.is(result.remaining, 4, 'remaining tokens correct')

  limiter.destroy()
})

test('RateLimiter - blocks after exhaustion', async (t) => {
  const limiter = new RateLimiter({
    limits: {
      'notes': { tokens: 2, refillRate: 1, refillInterval: 60000 }
    }
  })

  await limiter.check('user1', 'notes')
  await limiter.check('user1', 'notes')
  const result = await limiter.check('user1', 'notes')

  t.absent(result.allowed, 'third request blocked')
  t.is(result.remaining, 0, 'no remaining tokens')
  t.ok(result.retryAfter > 0, 'has retryAfter')

  limiter.destroy()
})

test('RateLimiter - different keys tracked independently', async (t) => {
  const limiter = new RateLimiter({
    limits: {
      'notes': { tokens: 1, refillRate: 1, refillInterval: 60000 }
    }
  })

  const r1 = await limiter.check('user1', 'notes')
  const r2 = await limiter.check('user2', 'notes')

  t.ok(r1.allowed, 'user1 allowed')
  t.ok(r2.allowed, 'user2 allowed')

  limiter.destroy()
})

test('RateLimiter - unknown operation returns allowed', async (t) => {
  const limiter = new RateLimiter()

  const result = await limiter.check('user1', 'unknown-op')
  t.ok(result.allowed, 'unknown operations allowed')

  limiter.destroy()
})

test('RateLimiter - reset restores tokens', async (t) => {
  const limiter = new RateLimiter({
    limits: {
      'notes': { tokens: 1, refillRate: 0, refillInterval: 60000 }
    }
  })

  await limiter.check('user1', 'notes') // exhaust
  const blocked = await limiter.check('user1', 'notes')
  t.absent(blocked.allowed, 'blocked after exhaustion')

  await limiter.reset('user1', 'notes')
  const afterReset = await limiter.check('user1', 'notes')
  t.ok(afterReset.allowed, 'allowed after reset')

  limiter.destroy()
})

test('RateLimiter - notes operation exists in defaults', async (t) => {
  const limiter = new RateLimiter()

  t.ok(limiter.limits['notes'], 'notes operation exists in defaults')
  t.absent(limiter.limits['git'], 'git operation does NOT exist in defaults')

  limiter.destroy()
})