Skip to content

Commit

Permalink
Tests/use fake timers (#218)
Browse files Browse the repository at this point in the history
* test: convert actual timers to fake timers for tests that doesn't use Redis

* test: convert actual timers to fake timers for tests that doesn't use Redis

* chore(tests): fix grammar of a comment

Co-authored-by: Frazer Smith <[email protected]>

Co-authored-by: Raz Luvaton <[email protected]>
Co-authored-by: Frazer Smith <[email protected]>
  • Loading branch information
3 people authored Feb 14, 2022
1 parent 3ff788e commit d53bb4b
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 35 deletions.
2 changes: 1 addition & 1 deletion store/LocalStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ LocalStore.prototype.incr = function (ip, cb) {
cb(null, { current, ttl: this.timeWindow })
} else {
// start counting from the first request/increment
if (!this.msLastBeat) {
if (this.msLastBeat === undefined || this.msLastBeat === null) {
this.msLastBeat = Date.now()
}

Expand Down
80 changes: 59 additions & 21 deletions test/global-rate-limit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const REDIS_HOST = '127.0.0.1'

test('Basic', async t => {
t.plan(15)
t.context.clock = FakeTimers.install()
const fastify = Fastify()
fastify.register(rateLimit, { max: 2, timeWindow: 1000 })

Expand Down Expand Up @@ -45,18 +46,22 @@ test('Basic', async t => {
message: 'Rate limit exceeded, retry in 1 second'
}, JSON.parse(res.payload))

// TODO - use sinom timers
await sleep(1100)
t.context.clock.tick(1100)

res = await fastify.inject('/')

t.equal(res.statusCode, 200)
t.equal(res.headers['x-ratelimit-limit'], 2)
t.equal(res.headers['x-ratelimit-remaining'], 1)

t.teardown(() => {
t.context.clock.uninstall()
})
})

test('With text timeWindow', async t => {
t.plan(15)
t.context.clock = FakeTimers.install()
const fastify = Fastify()
fastify.register(rateLimit, { max: 2, timeWindow: '1s' })

Expand Down Expand Up @@ -89,14 +94,17 @@ test('With text timeWindow', async t => {
message: 'Rate limit exceeded, retry in 1 second'
}, JSON.parse(res.payload))

// TODO - use sinom timers
await sleep(1100)
t.context.clock.tick(1100)

res = await fastify.inject('/')

t.equal(res.statusCode, 200)
t.equal(res.headers['x-ratelimit-limit'], 2)
t.equal(res.headers['x-ratelimit-remaining'], 1)

t.teardown(() => {
t.context.clock.uninstall()
})
})

test('When passing NaN to the timeWindow property then the timeWindow should be the default value - 60 seconds', async t => {
Expand Down Expand Up @@ -271,7 +279,7 @@ test('With redis store', async t => {
message: 'Rate limit exceeded, retry in 1 second'
}, JSON.parse(res.payload))

// TODO - use sinom timers
// Not using fake timers here as we use an external Redis that would not be effected by this
await sleep(1100)

res = await fastify.inject('/')
Expand Down Expand Up @@ -320,6 +328,7 @@ test('Skip on redis error', async t => {

test('With keyGenerator', async t => {
t.plan(19)
t.context.clock = FakeTimers.install()
const fastify = Fastify()
fastify.register(rateLimit, {
max: 2,
Expand Down Expand Up @@ -364,13 +373,16 @@ test('With keyGenerator', async t => {
message: 'Rate limit exceeded, retry in 1 second'
}, JSON.parse(res.payload))

// TODO - use sinom timers
await sleep(1100)
t.context.clock.tick(1100)

res = await fastify.inject(payload)
t.equal(res.statusCode, 200)
t.equal(res.headers['x-ratelimit-limit'], 2)
t.equal(res.headers['x-ratelimit-remaining'], 1)

t.teardown(() => {
t.context.clock.uninstall()
})
})

test('With CustomStore', async t => {
Expand Down Expand Up @@ -572,6 +584,7 @@ test('when passing NaN to max variable then it should use the default max - 1000

test('hide rate limit headers', async t => {
t.plan(14)
t.context.clock = FakeTimers.install()
const fastify = Fastify()
fastify.register(rateLimit, {
max: 1,
Expand Down Expand Up @@ -604,18 +617,22 @@ test('hide rate limit headers', async t => {
t.notOk(res.headers['x-ratelimit-reset'], 'the header must be missing')
t.notOk(res.headers['retry-after'], 'the header must be missing')

// TODO - use sinom timers
await sleep(1100)
t.context.clock.tick(1100)

res = await fastify.inject('/')
t.equal(res.statusCode, 200)
t.equal(res.headers['x-ratelimit-limit'], 1)
t.equal(res.headers['x-ratelimit-remaining'], 0)
t.equal(res.headers['x-ratelimit-reset'], 1)

t.teardown(() => {
t.context.clock.uninstall()
})
})

test('hide rate limit headers on exceeding', async t => {
t.plan(14)
t.context.clock = FakeTimers.install()
const fastify = Fastify()
fastify.register(rateLimit, {
max: 1,
Expand Down Expand Up @@ -647,19 +664,23 @@ test('hide rate limit headers on exceeding', async t => {
t.not(res.headers['x-ratelimit-reset'], undefined)
t.equal(res.headers['retry-after'], 1000)

// TODO - use sinom timers
await sleep(1100)
t.context.clock.tick(1100)

res = await fastify.inject('/')

t.equal(res.statusCode, 200)
t.notOk(res.headers['x-ratelimit-limit'], 'the header must be missing')
t.notOk(res.headers['x-ratelimit-remaining'], 'the header must be missing')
t.notOk(res.headers['x-ratelimit-reset'], 'the header must be missing')

t.teardown(() => {
t.context.clock.uninstall()
})
})

test('hide rate limit headers at all times', async t => {
t.plan(14)
t.context.clock = FakeTimers.install()
const fastify = Fastify()
fastify.register(rateLimit, {
max: 1,
Expand Down Expand Up @@ -697,15 +718,18 @@ test('hide rate limit headers at all times', async t => {
t.notOk(res.headers['x-ratelimit-reset'], 'the header must be missing')
t.notOk(res.headers['retry-after'], 'the header must be missing')

// TODO - use sinom timers
await sleep(1100)
t.context.clock.tick(1100)

res = await fastify.inject('/')

t.equal(res.statusCode, 200)
t.notOk(res.headers['x-ratelimit-limit'], 'the header must be missing')
t.notOk(res.headers['x-ratelimit-remaining'], 'the header must be missing')
t.notOk(res.headers['x-ratelimit-reset'], 'the header must be missing')

t.teardown(() => {
t.context.clock.uninstall()
})
})

test('With ban', async t => {
Expand Down Expand Up @@ -758,6 +782,7 @@ test('stops fastify lifecycle after onRequest and before preValidation', async t

test('With enabled IETF Draft Spec', async t => {
t.plan(16)
t.context.clock = FakeTimers.install()
const fastify = Fastify()
fastify.register(rateLimit, {
max: 2,
Expand Down Expand Up @@ -802,18 +827,22 @@ test('With enabled IETF Draft Spec', async t => {
message: 'Rate limit exceeded, retry in 1 second'
}, payload)

// TODO - use sinom timers
await sleep(1100)
t.context.clock.tick(1100)

res = await fastify.inject('/')

t.equal(res.statusCode, 200)
t.equal(res.headers['ratelimit-limit'], 2)
t.equal(res.headers['ratelimit-remaining'], 1)

t.teardown(() => {
t.context.clock.uninstall()
})
})

test('hide IETF draft spec headers', async t => {
t.plan(14)
t.context.clock = FakeTimers.install()
const fastify = Fastify()
fastify.register(rateLimit, {
max: 1,
Expand Down Expand Up @@ -847,19 +876,23 @@ test('hide IETF draft spec headers', async t => {
t.notOk(res.headers['ratelimit-reset'], 'the header must be missing')
t.notOk(res.headers['retry-after'], 'the header must be missing')

// TODO - use sinom timers
await sleep(1100)
t.context.clock.tick(1100)

res = await fastify.inject('/')

t.equal(res.statusCode, 200)
t.equal(res.headers['ratelimit-limit'], 1)
t.equal(res.headers['ratelimit-remaining'], 0)
t.equal(res.headers['ratelimit-reset'], 1)

t.teardown(() => {
t.context.clock.uninstall()
})
})

test('afterReset and Rate Limit remain the same when enableDraftSpec is enabled', async t => {
t.plan(13)
t.context.clock = FakeTimers.install()
const fastify = Fastify()
fastify.register(rateLimit, {
max: 1,
Expand All @@ -875,10 +908,11 @@ test('afterReset and Rate Limit remain the same when enableDraftSpec is enabled'
t.equal(res.headers['ratelimit-limit'], 1)
t.equal(res.headers['ratelimit-remaining'], 0)

await Promise.all([
sleep(500).then(retry.bind(null, 9)),
sleep(1500).then(retry.bind(null, 8))
])
t.context.clock.tick(500)
await retry(9)

t.context.clock.tick(1000)
await retry(8)

async function retry (timeLeft) {
const res = await fastify.inject('/')
Expand All @@ -889,6 +923,10 @@ test('afterReset and Rate Limit remain the same when enableDraftSpec is enabled'
t.equal(res.headers['ratelimit-reset'], timeLeft)
t.equal(res.headers['ratelimit-reset'], res.headers['retry-after'])
}

t.teardown(() => {
t.context.clock.uninstall()
})
})

test('Before async in "max"', async t => {
Expand Down
Loading

0 comments on commit d53bb4b

Please sign in to comment.