mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-25 02:49:32 +01:00
181 lines
6.4 KiB
TypeScript
181 lines
6.4 KiB
TypeScript
import { BucketKeyMissingError, Limiter, Storage } from '../../src/utils/token-bucket'
|
|
|
|
describe('Storage', () => {
|
|
describe('replenish()', () => {
|
|
afterEach(() => {
|
|
jest.useRealTimers()
|
|
})
|
|
|
|
it('adds capacity to new key', () => {
|
|
const key = 'test'
|
|
const storage = new Storage(10, 1)
|
|
const now = new Date('2023-02-08T08:00:00')
|
|
jest.useFakeTimers().setSystemTime(now)
|
|
|
|
storage.replenish(key)
|
|
|
|
expect(storage.buckets.has(key)).toEqual(true)
|
|
expect(storage.buckets.get(key)![0]).toEqual(10)
|
|
expect(storage.buckets.get(key)![1]).toEqual(now.valueOf())
|
|
})
|
|
|
|
it('adds rate to existing key', () => {
|
|
const key = 'test'
|
|
const storage = new Storage(10, 1)
|
|
const now = new Date('2023-02-08T08:00:00')
|
|
jest.useFakeTimers().setSystemTime(now)
|
|
|
|
storage.replenish(key)
|
|
storage.consume(key, 5)
|
|
now.setSeconds(now.getSeconds() + 2)
|
|
jest.setSystemTime(now)
|
|
storage.replenish(key)
|
|
|
|
expect(storage.buckets.has(key)).toEqual(true)
|
|
expect(storage.buckets.get(key)![0]).toEqual(7)
|
|
expect(storage.buckets.get(key)![1]).toEqual(now.valueOf())
|
|
})
|
|
|
|
it('adds rate to existing key with argument now', () => {
|
|
const key = 'test'
|
|
const storage = new Storage(10, 1)
|
|
const now = Date.now()
|
|
|
|
storage.replenish(key, now)
|
|
storage.consume(key, 9)
|
|
storage.replenish(key, now + 2000)
|
|
|
|
expect(storage.buckets.has(key)).toEqual(true)
|
|
expect(storage.buckets.get(key)![0]).toEqual(3)
|
|
expect(storage.buckets.get(key)![1]).toEqual(now + 2000)
|
|
})
|
|
|
|
it('does not add more than capacity tokens', () => {
|
|
const key = 'test'
|
|
const storage = new Storage(10, 1)
|
|
const now = new Date('2023-02-08T08:00:00')
|
|
|
|
storage.replenish(key, now.valueOf())
|
|
expect(storage.buckets.get(key)![0]).toEqual(10)
|
|
expect(storage.buckets.get(key)![1]).toEqual(now.valueOf())
|
|
|
|
// get two tokens to be replenished
|
|
storage.consume(key, 2)
|
|
expect(storage.buckets.get(key)![0]).toEqual(8)
|
|
|
|
// 20 seconds would exceed capacity of 10 tokens at 1 token/sec.
|
|
storage.replenish(key, now.valueOf() + 20000)
|
|
|
|
expect(storage.buckets.has(key)).toEqual(true)
|
|
expect(storage.buckets.get(key)![0]).toEqual(10)
|
|
expect(storage.buckets.get(key)![1]).toEqual(now.valueOf() + 20000)
|
|
})
|
|
|
|
it('does not add if now is in the past', () => {
|
|
const key = 'test'
|
|
const storage = new Storage(10, 1)
|
|
const now = new Date('2023-02-08T08:00:00')
|
|
|
|
storage.replenish(key, now.valueOf())
|
|
expect(storage.buckets.get(key)![0]).toEqual(10)
|
|
expect(storage.buckets.get(key)![1]).toEqual(now.valueOf())
|
|
|
|
// get two tokens to be replenished
|
|
storage.consume(key, 2)
|
|
expect(storage.buckets.get(key)![0]).toEqual(8)
|
|
|
|
// Will be a no-op due to a lower now value
|
|
storage.replenish(key, now.valueOf() - 20000)
|
|
|
|
expect(storage.buckets.has(key)).toEqual(true)
|
|
expect(storage.buckets.get(key)![0]).toEqual(8)
|
|
expect(storage.buckets.get(key)![1]).toEqual(now.valueOf())
|
|
})
|
|
})
|
|
|
|
describe('consume()', () => {
|
|
it('consumes when tokens are less than capacity', () => {
|
|
const key = 'test'
|
|
const storage = new Storage(10, 1)
|
|
|
|
storage.replenish(key)
|
|
|
|
expect(storage.consume(key, 9)).toEqual(true)
|
|
expect(storage.buckets.get(key)![0]).toEqual(1)
|
|
})
|
|
|
|
it('rejects when tokens are more than capacity', () => {
|
|
const key = 'test'
|
|
const storage = new Storage(10, 1)
|
|
|
|
storage.replenish(key)
|
|
|
|
expect(storage.consume(key, 11)).toEqual(false)
|
|
expect(storage.buckets.get(key)![0]).toEqual(10)
|
|
})
|
|
|
|
it('throws error on missing bucket key', () => {
|
|
const key = 'test'
|
|
const storage = new Storage(10, 1)
|
|
|
|
expect(storage.buckets.has(key)).toEqual(false)
|
|
expect(() => storage.consume(key, 1)).toThrow(BucketKeyMissingError)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Limiter', () => {
|
|
describe('consume()', () => {
|
|
afterEach(() => {
|
|
jest.useRealTimers()
|
|
})
|
|
|
|
it('consumes when tokens available', () => {
|
|
const key = 'test'
|
|
const limiter = new Limiter(10, 2)
|
|
const now = new Date('2023-02-08T08:00:00')
|
|
jest.useFakeTimers().setSystemTime(now)
|
|
|
|
expect(limiter.consume(key, 9)).toEqual(true)
|
|
})
|
|
|
|
it('rejects when tokens run out', () => {
|
|
const key = 'test'
|
|
const limiter = new Limiter(10, 2)
|
|
const now = new Date('2023-02-08T08:00:00')
|
|
jest.useFakeTimers().setSystemTime(now)
|
|
|
|
expect(limiter.consume(key, 10)).toEqual(true)
|
|
// We are not advancing time, so no tokens should have been replenished
|
|
expect(limiter.consume(key, 1)).toEqual(false)
|
|
})
|
|
|
|
it('consumes when tokens have been replenished', () => {
|
|
const key = 'test'
|
|
const limiter = new Limiter(10, 2)
|
|
const now = new Date('2023-02-08T08:00:00')
|
|
|
|
jest.useFakeTimers().setSystemTime(now)
|
|
limiter.consume(key, 10)
|
|
|
|
expect(limiter.consume(key, 1)).toEqual(false)
|
|
|
|
jest.setSystemTime(now.valueOf() + 1000)
|
|
// Now that we have advanced 1 second, we can consume 1 token
|
|
expect(limiter.consume(key, 1)).toEqual(true)
|
|
})
|
|
|
|
it('consumes when tokens have been replenished with now argument', () => {
|
|
const key = 'test'
|
|
const limiter = new Limiter(10, 2)
|
|
const now = new Date('2023-02-08T08:00:00')
|
|
jest.useFakeTimers().setSystemTime(now)
|
|
|
|
limiter.consume(key, 10, now.valueOf())
|
|
expect(limiter.consume(key, 1)).toEqual(false)
|
|
// Even though we are not advancing time, we are passing the time to use with now
|
|
expect(limiter.consume(key, 1, now.valueOf() + 1000)).toEqual(true)
|
|
})
|
|
})
|
|
})
|