@@ -70,6 +70,30 @@ describe('rateLimit', () => {
7070 expect ( result . remaining ) . toBe ( limit - 1 ) ;
7171 } ) ;
7272
73+ it ( 'expires at the window boundary after sliding requests' , async ( ) => {
74+ const ip = '7.7.7.7' ;
75+ const windowMs = 60000 ;
76+ const limit = 3 ;
77+
78+ vi . setSystemTime ( 0 ) ;
79+ await rateLimit ( ip , limit , windowMs ) ;
80+ vi . advanceTimersByTime ( 20000 ) ;
81+ await rateLimit ( ip , limit , windowMs ) ;
82+ vi . advanceTimersByTime ( 20000 ) ;
83+ await rateLimit ( ip , limit , windowMs ) ;
84+
85+ // Still within the same fixed window, before the boundary
86+ vi . advanceTimersByTime ( 19999 ) ;
87+ expect ( ( await rateLimit ( ip , limit , windowMs ) ) . success ) . toBe ( false ) ;
88+
89+ // Move just past the window limit. The old entry should have expired.
90+ vi . advanceTimersByTime ( 2 ) ;
91+
92+ const result = await rateLimit ( ip , limit , windowMs ) ;
93+ expect ( result . success ) . toBe ( true ) ;
94+ expect ( result . remaining ) . toBe ( limit - 1 ) ;
95+ } ) ;
96+
7397 it ( 'tracks different IPs separately' , async ( ) => {
7498 const ip1 = '11.11.11.11' ;
7599 const ip2 = '22.22.22.22' ;
0 commit comments