@@ -50,6 +50,17 @@ describe('Test TokenBucket Rate Limiter', () => {
5050 afterEach ( ( ) => {
5151 client . flushall ( ) ;
5252 } ) ;
53+ test ( 'fixed window and cache are initially empty' , async ( ) => {
54+ // window is intially empty
55+ const withdraw5 = 5 ;
56+ expect ( ( await limiter . processRequest ( user1 , timestamp , withdraw5 ) ) . tokens ) . toBe (
57+ CAPACITY - withdraw5
58+ ) ;
59+ const tokenCountFull = await getWindowFromClient ( client , user1 ) ;
60+ expect ( tokenCountFull . currentTokens ) . toBe ( CAPACITY - withdraw5 ) ;
61+ expect ( tokenCountFull . previousTokens ) . toBe ( 0 ) ;
62+ } ) ;
63+
5364 test ( 'fixed window is initially empty' , async ( ) => {
5465 // window is intially empty
5566 const withdraw5 = 5 ;
@@ -58,6 +69,7 @@ describe('Test TokenBucket Rate Limiter', () => {
5869 ) ;
5970 const tokenCountFull = await getWindowFromClient ( client , user1 ) ;
6071 expect ( tokenCountFull . currentTokens ) . toBe ( CAPACITY - withdraw5 ) ;
72+ expect ( tokenCountFull . previousTokens ) . toBe ( 0 ) ;
6173 } ) ;
6274
6375 test ( 'fixed window is partially full and request has leftover tokens' , async ( ) => {
@@ -92,9 +104,16 @@ describe('Test TokenBucket Rate Limiter', () => {
92104 // tokens returned in processRequest is equal to the capacity
93105 // still available in the fixed window
94106
95- expect (
96- ( await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE + 1 , 1 ) ) . tokens
97- ) . toBe ( 0 ) ; // here, we expect the rolling window to only allow 1 token, b/c
107+ // adds additional ms so that:
108+ // rolling window proportion: .99999...
109+ // 1 + 10 * .9 = 10 (floored)
110+ const result = await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE + 1 , 1 ) ;
111+
112+ // should be allowed because formula is floored
113+ expect ( result . success ) . toBe ( true ) ;
114+ expect ( result . tokens ) . toBe ( 0 ) ;
115+
116+ // here, we expect the rolling window to only allow 1 token, b/c
98117 // only 1ms has passed since the previous fixed window
99118
100119 // `currentTokens` cached is the amount of tokens
@@ -105,7 +124,31 @@ describe('Test TokenBucket Rate Limiter', () => {
105124 expect ( count . currentTokens ) . toBe ( 1 ) ;
106125 } ) ;
107126
108- // three different tests within, with different rolling window proportions (.25, .5, .75)
127+ // five different tests within, with different rolling window proportions (0.01, .25, .5, .75, 1)
128+ test ( 'rolling window at 100% allows requests under capacity' , async ( ) => {
129+ // 100% of rolling window present in previous fixed window
130+ // 1*60000 = 60000 (time after initial fixedWindowStart
131+ // to set rolling window at 100% of previous fixed window)
132+
133+ // to set initial fixedWindowStart
134+ await setTokenCountInClient ( client , user4 , 0 , 0 , timestamp ) ;
135+
136+ // large request at very end of first fixed window
137+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , 8 ) ;
138+
139+ // 2 + 8 * 1 = 10, right at capacity (request should be allowed)
140+ // tokens until capacity: 0 (tokens property returned by processRequest method)
141+ const result = await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE , 2 ) ;
142+ expect ( result . tokens ) . toBe ( 0 ) ;
143+ expect ( result . success ) . toBe ( true ) ;
144+
145+ // currentTokens (in current fixed window): 4
146+ // previousTokens (in previous fixed window): 8
147+ const count1 = await getWindowFromClient ( client , user4 ) ;
148+ expect ( count1 . currentTokens ) . toBe ( 2 ) ;
149+ expect ( count1 . previousTokens ) . toBe ( 8 ) ;
150+ } ) ;
151+
109152 test ( 'rolling window at 75% allows requests under capacity' , async ( ) => {
110153 // 75% of rolling window present in previous fixed window
111154 // 1.25*60000 = 75000 (time after initial fixedWindowStart
@@ -115,13 +158,17 @@ describe('Test TokenBucket Rate Limiter', () => {
115158 await setTokenCountInClient ( client , user4 , 0 , 0 , timestamp ) ;
116159
117160 // large request at very end of first fixed window
118- await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE , 8 ) ;
161+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , 8 ) ;
119162
120163 // 4 + 8 * .75 = 10, right at capacity (request should be allowed)
121164 // tokens until capacity: 0 (tokens property returned by processRequest method)
122- expect (
123- ( await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE * 1.25 , 4 ) ) . tokens
124- ) . toBe ( 0 ) ;
165+ const result = await limiter . processRequest (
166+ user4 ,
167+ timestamp + WINDOW_SIZE * 1.25 ,
168+ 4
169+ ) ;
170+ expect ( result . tokens ) . toBe ( 0 ) ;
171+ expect ( result . success ) . toBe ( true ) ;
125172
126173 // currentTokens (in current fixed window): 4
127174 // previousTokens (in previous fixed window): 8
@@ -139,13 +186,17 @@ describe('Test TokenBucket Rate Limiter', () => {
139186 await setTokenCountInClient ( client , user4 , 0 , 0 , timestamp ) ;
140187
141188 // large request at very end of first fixed window
142- await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE , 8 ) ;
189+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , 8 ) ;
143190
144191 // 4 + 8 * .5 = 8, under capacity (request should be allowed)
145192 // tokens until capacity: 2 (tokens property returned by processRequest method)
146- expect (
147- ( await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE * 1.5 , 4 ) ) . tokens
148- ) . toBe ( 2 ) ;
193+ const result = await limiter . processRequest (
194+ user4 ,
195+ timestamp + WINDOW_SIZE * 1.5 ,
196+ 4
197+ ) ;
198+ expect ( result . tokens ) . toBe ( 2 ) ;
199+ expect ( result . success ) . toBe ( true ) ;
149200
150201 // currentTokens (in current fixed window): 4
151202 // previousTokens (in previous fixed window): 8
@@ -163,20 +214,52 @@ describe('Test TokenBucket Rate Limiter', () => {
163214 await setTokenCountInClient ( client , user4 , 0 , 0 , timestamp ) ;
164215
165216 // large request at very end of first fixed window
166- await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE , 8 ) ;
217+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , 8 ) ;
167218
168219 // 4 + 8 * .25 = 6, under capacity (request should be allowed)
169220 // tokens until capacity: 4 (tokens property returned by processRequest method)
170- expect (
171- ( await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE * 1.75 , 4 ) ) . tokens
172- ) . toBe ( 4 ) ;
221+ const result = await limiter . processRequest (
222+ user4 ,
223+ timestamp + WINDOW_SIZE * 1.75 ,
224+ 4
225+ ) ;
226+ expect ( result . tokens ) . toBe ( 4 ) ;
227+ expect ( result . success ) . toBe ( true ) ;
173228
174229 // currentTokens (in current fixed window): 4
175230 // previousTokens (in previous fixed window): 8
176231 const count = await getWindowFromClient ( client , user4 ) ;
177232 expect ( count . currentTokens ) . toBe ( 4 ) ;
178233 expect ( count . previousTokens ) . toBe ( 8 ) ;
179234 } ) ;
235+
236+ test ( 'rolling window at 1% allows requests under capacity' , async ( ) => {
237+ // 1% of rolling window present in previous fixed window
238+ // 0.01*60000 = 600 (time after initial fixedWindowStart
239+ // to set rolling window at 1% of previous fixed window)
240+
241+ // to set initial fixedWindowStart
242+ await setTokenCountInClient ( client , user4 , 0 , 0 , timestamp ) ;
243+
244+ // large request at very end of first fixed window
245+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , 8 ) ;
246+
247+ // 10 + 8 * .01 = 10, right at capacity (request should be allowed)
248+ // tokens until capacity: 0 (tokens property returned by processRequest method)
249+ const result = await limiter . processRequest (
250+ user4 ,
251+ timestamp + WINDOW_SIZE * 1.99 ,
252+ 4
253+ ) ;
254+ expect ( result . tokens ) . toBe ( 0 ) ;
255+ expect ( result . success ) . toBe ( true ) ;
256+
257+ // currentTokens (in current fixed window): 4
258+ // previousTokens (in previous fixed window): 8
259+ const count1 = await getWindowFromClient ( client , user4 ) ;
260+ expect ( count1 . currentTokens ) . toBe ( 4 ) ;
261+ expect ( count1 . previousTokens ) . toBe ( 8 ) ;
262+ } ) ;
180263 } ) ;
181264
182265 describe ( 'after a BLOCKED request...' , ( ) => {
@@ -196,15 +279,40 @@ describe('Test TokenBucket Rate Limiter', () => {
196279
197280 await setTokenCountInClient ( client , user2 , initRequest , 0 , timestamp ) ;
198281 // expect remaining tokens to be 4, b/c the 5 token request should be blocked
199- expect (
200- ( await limiter . processRequest ( user2 , timestamp + WINDOW_SIZE , 5 ) ) . tokens
201- ) . toBe ( CAPACITY - initRequest ) ;
282+ const result = await limiter . processRequest ( user2 , timestamp + WINDOW_SIZE - 1 , 5 ) ;
283+
284+ expect ( result . success ) . toBe ( false ) ;
285+ expect ( result . tokens ) . toBe ( CAPACITY - initRequest ) ;
202286
203287 // expect current tokens in the window to still be 6
204288 expect ( ( await getWindowFromClient ( client , user2 ) ) . currentTokens ) . toBe ( 6 ) ;
205289 } ) ;
206290
207- // 3 rolling window tests with different proportions (.25, .5, .75)
291+ // 5 rolling window tests with different proportions (.01, .25, .5, .75, 1)
292+ test ( 'rolling window at 100% blocks requests over allowed limit set by formula' , async ( ) => {
293+ // 100% of rolling window present in previous fixed window
294+ // 1*60000 = 60000 (time after initial fixedWindowStart
295+ // to set rolling window at 100% of previous fixed window)
296+
297+ // to set initial fixedWindowStart
298+ await setTokenCountInClient ( client , user4 , 0 , 0 , timestamp ) ;
299+
300+ const initRequest = 8 ;
301+
302+ // large request at very end of first fixed window
303+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , initRequest ) ;
304+
305+ // 3 + 8 * 1 = 11, above capacity (request should be blocked)
306+ const result = await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE , 3 ) ;
307+ expect ( result . tokens ) . toBe ( 10 ) ;
308+ expect ( result . success ) . toBe ( false ) ;
309+
310+ // currentTokens (in current fixed window): 0
311+ // previousTokens (in previous fixed window): 8
312+ const count1 = await getWindowFromClient ( client , user4 ) ;
313+ expect ( count1 . currentTokens ) . toBe ( 0 ) ;
314+ expect ( count1 . previousTokens ) . toBe ( initRequest ) ;
315+ } ) ;
208316 test ( 'rolling window at 75% blocks requests over allowed limit set by formula' , async ( ) => {
209317 // 75% of rolling window present in previous fixed window
210318 // 1.25*60000 = 75000 (time after initial fixedWindowStart
@@ -216,12 +324,16 @@ describe('Test TokenBucket Rate Limiter', () => {
216324 const initRequest = 8 ;
217325
218326 // large request at very end of first fixed window
219- await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE , initRequest ) ;
327+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , initRequest ) ;
220328
221329 // 5 + 8 * .75 = 11, above capacity (request should be blocked)
222- expect (
223- ( await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE * 1.25 , 5 ) ) . tokens
224- ) . toBe ( 10 ) ;
330+ const result = await limiter . processRequest (
331+ user4 ,
332+ timestamp + WINDOW_SIZE * 1.25 ,
333+ 5
334+ ) ;
335+ expect ( result . tokens ) . toBe ( 10 ) ;
336+ expect ( result . success ) . toBe ( false ) ;
225337
226338 // currentTokens (in current fixed window): 0
227339 // previousTokens (in previous fixed window): 8
@@ -242,12 +354,12 @@ describe('Test TokenBucket Rate Limiter', () => {
242354 const initRequest = 8 ;
243355
244356 // large request at very end of first fixed window
245- await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE , initRequest ) ;
357+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , initRequest ) ;
246358
247359 // 7 + 8 * .5 = 11, over capacity (request should be blocked)
248- expect (
249- ( await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE * 1.5 , 7 ) ) . tokens
250- ) . toBe ( 10 ) ;
360+ const result = await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE * 1.5 , 7 ) ;
361+ expect ( result . tokens ) . toBe ( 10 ) ;
362+ expect ( result . success ) . toBe ( false ) ;
251363
252364 // currentTokens (in current fixed window): 0
253365 // previousTokens (in previous fixed window): 8
@@ -267,19 +379,43 @@ describe('Test TokenBucket Rate Limiter', () => {
267379 const initRequest = 8 ;
268380
269381 // large request at very end of first fixed window
270- await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE , initRequest ) ;
382+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , initRequest ) ;
271383
272384 // 9 + 8 * .25 = 11, over capacity (request should be blocked)
273- expect (
274- ( await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE * 1.75 , 9 ) ) . tokens
275- ) . toBe ( 10 ) ;
385+ const result = await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE * 1.75 , 9 ) ;
386+ expect ( result . tokens ) . toBe ( 10 ) ;
387+ expect ( result . success ) . toBe ( false ) ;
276388
277389 // currentTokens (in current fixed window): 0
278390 // previousTokens (in previous fixed window): 8
279391 const count = await getWindowFromClient ( client , user4 ) ;
280392 expect ( count . currentTokens ) . toBe ( 0 ) ;
281393 expect ( count . previousTokens ) . toBe ( initRequest ) ;
282394 } ) ;
395+ test ( 'rolling window at 100% blocks requests over allowed limit set by formula' , async ( ) => {
396+ // 1% of rolling window present in previous fixed window
397+ // .01*60000 = 600 (time after initial fixedWindowStart
398+ // to set rolling window at 100% of previous fixed window)
399+
400+ // to set initial fixedWindowStart
401+ await setTokenCountInClient ( client , user4 , 0 , 0 , timestamp ) ;
402+
403+ const initRequest = 8 ;
404+
405+ // large request at very end of first fixed window
406+ await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE - 1 , initRequest ) ;
407+
408+ // 11 + 8 * .01 = 11, above capacity (request should be blocked)
409+ const result = await limiter . processRequest ( user4 , timestamp + WINDOW_SIZE , 11 ) ;
410+ expect ( result . tokens ) . toBe ( 10 ) ;
411+ expect ( result . success ) . toBe ( false ) ;
412+
413+ // currentTokens (in current fixed window): 0
414+ // previousTokens (in previous fixed window): 8
415+ const count1 = await getWindowFromClient ( client , user4 ) ;
416+ expect ( count1 . currentTokens ) . toBe ( 0 ) ;
417+ expect ( count1 . previousTokens ) . toBe ( initRequest ) ;
418+ } ) ;
283419 } ) ;
284420
285421 describe ( 'SlidingWindowCounter functions as expected' , ( ) => {
@@ -327,14 +463,14 @@ describe('Test TokenBucket Rate Limiter', () => {
327463 // fills second window with 4 tokens
328464 expect (
329465 await (
330- await limiter . processRequest ( user1 , timestamp + WINDOW_SIZE + 1 , 4 )
466+ await limiter . processRequest ( user1 , timestamp + WINDOW_SIZE , 4 )
331467 ) . tokens
332468 ) . toBe ( 2 ) ;
333469 // currentTokens (in current fixed window): 0
334470 // previousTokens (in previous fixed window): 8
335471 const count = await getWindowFromClient ( client , user1 ) ;
336472 // ensures that fixed window is updated when a request goes over
337- expect ( count . fixedWindowStart ) . toBe ( timestamp + WINDOW_SIZE + 1 ) ;
473+ expect ( count . fixedWindowStart ) . toBe ( timestamp + WINDOW_SIZE ) ;
338474 // ensures that previous tokens property updates on fixed window change
339475 expect ( count . previousTokens ) . toBe ( 5 ) ;
340476 // ensures that current tokens only represents tokens from current window requests
@@ -416,6 +552,21 @@ describe('Test TokenBucket Rate Limiter', () => {
416552 false
417553 ) ;
418554 } ) ;
555+
556+ test ( 'updates correctly when > WINDOW_SIZE * 2 has surpassed' , async ( ) => {
557+ await setTokenCountInClient ( client , user1 , 1 , 0 , timestamp ) ;
558+
559+ // to make sure that previous tokens is not 1
560+ const result = await limiter . processRequest ( user1 , timestamp + WINDOW_SIZE * 2 , 1 ) ;
561+
562+ expect ( result . tokens ) . toBe ( 9 ) ;
563+
564+ const redisData : RedisWindow = await getWindowFromClient ( client , user1 ) ;
565+
566+ expect ( redisData . currentTokens ) . toBe ( 1 ) ;
567+ expect ( redisData . previousTokens ) . toBe ( 0 ) ;
568+ expect ( redisData . fixedWindowStart ) . toBe ( timestamp + WINDOW_SIZE * 2 ) ;
569+ } ) ;
419570 } ) ;
420571
421572 describe ( 'SlidingWindowCounter correctly updates Redis cache' , ( ) => {
@@ -447,12 +598,14 @@ describe('Test TokenBucket Rate Limiter', () => {
447598
448599 expect ( redisData . currentTokens ) . toBe ( 2 ) ;
449600
450- await limiter . processRequest ( user1 , timestamp + WINDOW_SIZE + 1 , 3 ) ;
601+ // new window
602+ await limiter . processRequest ( user1 , timestamp + WINDOW_SIZE , 3 ) ;
451603
452604 redisData = await getWindowFromClient ( client , user1 ) ;
453605
454606 expect ( redisData . currentTokens ) . toBe ( 3 ) ;
455607 expect ( redisData . previousTokens ) . toBe ( 2 ) ;
608+ expect ( redisData . fixedWindowStart ) . toBe ( timestamp + WINDOW_SIZE ) ;
456609 } ) ;
457610
458611 test ( 'all windows should be able to be reset' , async ( ) => {
0 commit comments