1 | var chai = require('chai')
|
2 | , request = require('supertest')
|
3 | , sinon = require('sinon')
|
4 | , redis = require('redis').createClient()
|
5 | , v = require('valentine')
|
6 | , subject = require('../')
|
7 |
|
8 | chai.use(require('sinon-chai'))
|
9 |
|
10 | describe('rate-limiter', function () {
|
11 | var express, app, limiter
|
12 |
|
13 | beforeEach(function () {
|
14 | express = require('express')
|
15 | app = express()
|
16 | limiter = subject(app, redis)
|
17 | })
|
18 |
|
19 | afterEach(function (done) {
|
20 | redis.flushdb(done)
|
21 | })
|
22 |
|
23 | it('should work', function (done) {
|
24 | var map = [10, 9, 8, 7, 6, 5, 4, 3, 2]
|
25 | var clock = sinon.useFakeTimers()
|
26 |
|
27 | limiter({
|
28 | path: '/route',
|
29 | method: 'get',
|
30 | lookup: ['connection.remoteAddress'],
|
31 | total: 10,
|
32 | expire: 1000 * 60 * 60
|
33 | })
|
34 |
|
35 | app.get('/route', function (req, res) {
|
36 | res.send(200, 'hello')
|
37 | })
|
38 |
|
39 | var out = (map).map(function (item) {
|
40 | return function (f) {
|
41 | process.nextTick(function () {
|
42 | request(app)
|
43 | .get('/route')
|
44 | .expect('X-RateLimit-Limit', 10)
|
45 | .expect('X-RateLimit-Remaining', item - 1)
|
46 | .expect('X-RateLimit-Reset', 3600)
|
47 | .expect(200, function (e) {f(e)})
|
48 | })
|
49 | }
|
50 | })
|
51 | out.push(function (f) {
|
52 | request(app)
|
53 | .get('/route')
|
54 | .expect('X-RateLimit-Limit', 10)
|
55 | .expect('X-RateLimit-Remaining', 0)
|
56 | .expect('X-RateLimit-Reset', 3600)
|
57 | .expect('Retry-After', /\d+/)
|
58 | .expect(429, function (e) {f(e)})
|
59 | })
|
60 | out.push(function (f) {
|
61 |
|
62 | clock.tick(1000 * 60 * 60 + 1)
|
63 | request(app)
|
64 | .get('/route')
|
65 | .expect('X-RateLimit-Limit', 10)
|
66 | .expect('X-RateLimit-Remaining', 9)
|
67 | .expect('X-RateLimit-Reset', 7201)
|
68 | .expect(200, function (e) {
|
69 | clock.restore()
|
70 | f(e)
|
71 | })
|
72 | })
|
73 | v.waterfall(out, done)
|
74 | })
|
75 |
|
76 | context('options', function() {
|
77 | it('should process options.skipHeaders', function (done) {
|
78 | limiter({
|
79 | path: '/route',
|
80 | method: 'get',
|
81 | lookup: ['connection.remoteAddress'],
|
82 | total: 0,
|
83 | expire: 1000 * 60 * 60,
|
84 | skipHeaders: true
|
85 | })
|
86 |
|
87 | app.get('/route', function (req, res) {
|
88 | res.send(200, 'hello')
|
89 | })
|
90 |
|
91 | request(app)
|
92 | .get('/route')
|
93 | .expect(function(res) {
|
94 | if ('X-RateLimit-Limit' in res.headers) return 'X-RateLimit-Limit Header not to be set'
|
95 | })
|
96 | .expect(function(res) {
|
97 | if ('X-RateLimit-Remaining' in res.headers) return 'X-RateLimit-Remaining Header not to be set'
|
98 | })
|
99 | .expect(function(res) {
|
100 | if ('Retry-After' in res.headers) return 'Retry-After not to be set'
|
101 | })
|
102 | .expect(429, done)
|
103 | })
|
104 |
|
105 | it('should process ignoreErrors', function (done) {
|
106 | limiter({
|
107 | path: '/route',
|
108 | method: 'get',
|
109 | lookup: ['connection.remoteAddress'],
|
110 | total: 10,
|
111 | expire: 1000 * 60 * 60,
|
112 | ignoreErrors: true
|
113 | })
|
114 |
|
115 | app.get('/route', function (req, res) {
|
116 | res.send(200, 'hello')
|
117 | })
|
118 |
|
119 | var stub = sinon.stub(redis, 'get', function(key, callback) {
|
120 | callback({err: true})
|
121 | })
|
122 |
|
123 | request(app)
|
124 | .get('/route')
|
125 | .expect(200, function (e) {
|
126 | done(e)
|
127 | stub.restore()
|
128 | })
|
129 | })
|
130 | })
|
131 |
|
132 | context('direct middleware', function () {
|
133 |
|
134 | it('is able to mount without `path` and `method`', function (done) {
|
135 | var clock = sinon.useFakeTimers()
|
136 | var middleware = limiter({
|
137 | lookup: 'connection.remoteAddress',
|
138 | total: 3,
|
139 | expire: 1000 * 60 * 60
|
140 | })
|
141 | app.get('/direct', middleware, function (req, res, next) {
|
142 | res.send(200, 'is direct')
|
143 | })
|
144 | v.waterfall(
|
145 | function (f) {
|
146 | process.nextTick(function () {
|
147 | request(app)
|
148 | .get('/direct')
|
149 | .expect('X-RateLimit-Limit', 3)
|
150 | .expect('X-RateLimit-Remaining', 2)
|
151 | .expect(200, function (e) {f(e)})
|
152 | })
|
153 | },
|
154 | function (f) {
|
155 | process.nextTick(function () {
|
156 | request(app)
|
157 | .get('/direct')
|
158 | .expect('X-RateLimit-Limit', 3)
|
159 | .expect('X-RateLimit-Remaining', 1)
|
160 | .expect(200, function (e) {f(e)})
|
161 | })
|
162 | },
|
163 | function (f) {
|
164 | process.nextTick(function () {
|
165 | request(app)
|
166 | .get('/direct')
|
167 | .expect('X-RateLimit-Limit', 3)
|
168 | .expect('X-RateLimit-Remaining', 0)
|
169 | .expect('Retry-After', /\d+/)
|
170 | .expect(429, function (e) { f(null) })
|
171 | })
|
172 | },
|
173 | function (e) {
|
174 | done(e)
|
175 | }
|
176 | )
|
177 | })
|
178 | })
|
179 | })
|