@@ -31,7 +31,7 @@ function passThroughClientIp() {
3131 )
3232}
3333
34- import { enforceIpRateLimit , enforceUserRateLimit } from './route-helpers'
34+ import { enforceIpRateLimit , enforceUserOrIpRateLimit , enforceUserRateLimit } from './route-helpers'
3535
3636const consume = mockAdapter . consumeTokens as Mock
3737
@@ -157,4 +157,36 @@ describe('route-helpers rate limiting', () => {
157157 expect ( result ?. headers . get ( 'Retry-After' ) ) . toBe ( '60' )
158158 } )
159159 } )
160+
161+ describe ( 'enforceUserOrIpRateLimit' , ( ) => {
162+ beforeEach ( ( ) => {
163+ passThroughClientIp ( )
164+ } )
165+
166+ it ( 'keys per-user when userId is present' , async ( ) => {
167+ consume . mockResolvedValueOnce ( {
168+ allowed : true ,
169+ tokensRemaining : 59 ,
170+ resetAt : new Date ( ) ,
171+ } )
172+ const request = createMockRequest ( 'POST' , undefined , { 'x-forwarded-for' : '203.0.113.7' } )
173+
174+ await enforceUserOrIpRateLimit ( 'a2a-test' , 'user-1' , request )
175+
176+ expect ( consume ) . toHaveBeenCalledWith ( 'route:a2a-test:user:user-1' , 1 , expect . any ( Object ) )
177+ } )
178+
179+ it ( 'falls back to per-IP when userId is undefined' , async ( ) => {
180+ consume . mockResolvedValueOnce ( {
181+ allowed : true ,
182+ tokensRemaining : 59 ,
183+ resetAt : new Date ( ) ,
184+ } )
185+ const request = createMockRequest ( 'POST' , undefined , { 'x-forwarded-for' : '203.0.113.7' } )
186+
187+ await enforceUserOrIpRateLimit ( 'a2a-test' , undefined , request )
188+
189+ expect ( consume ) . toHaveBeenCalledWith ( 'route:a2a-test:ip:203.0.113.7' , 1 , expect . any ( Object ) )
190+ } )
191+ } )
160192} )
0 commit comments