@@ -242,6 +242,85 @@ public function testItCanThrottlePerSecond()
242
242
$ response ->assertOk ();
243
243
}
244
244
245
+ public function testItCanCombineRateLimitsWithoutSpecifyingUniqueKeys ()
246
+ {
247
+ $ rateLimiter = Container::getInstance ()->make (RateLimiter::class);
248
+ $ rateLimiter ->for ('test ' , fn () => [
249
+ Limit::perSecond (3 ),
250
+ Limit::perMinute (5 ),
251
+ ]);
252
+ Route::get ('/ ' , fn () => 'ok ' )->middleware (ThrottleRequests::using ('test ' ));
253
+
254
+ Carbon::setTestNow ('2000-01-01 00:00:00.000 ' );
255
+
256
+ // Make 3 requests, each a 100ms apart, that should all be successful.
257
+
258
+ for ($ i = 0 ; $ i < 3 ; $ i ++) {
259
+ match ($ i ) {
260
+ 0 => $ this ->assertSame ('2000-01-01 00:00:00.000 ' , now ()->toDateTimeString ('m ' )),
261
+ 1 => $ this ->assertSame ('2000-01-01 00:00:00.100 ' , now ()->toDateTimeString ('m ' )),
262
+ 2 => $ this ->assertSame ('2000-01-01 00:00:00.200 ' , now ()->toDateTimeString ('m ' )),
263
+ };
264
+
265
+ $ response = $ this ->get ('/ ' );
266
+ $ response ->assertOk ();
267
+ $ response ->assertContent ('ok ' );
268
+
269
+ Carbon::setTestNow (now ()->addMilliseconds (100 ));
270
+ }
271
+
272
+ // It is now 300 milliseconds past and we will make another request
273
+ // that should be rate limited.
274
+
275
+ $ this ->assertSame ('2000-01-01 00:00:00.300 ' , now ()->toDateTimeString ('m ' ));
276
+
277
+ $ response = $ this ->get ('/ ' );
278
+ $ response ->assertStatus (429 );
279
+ $ response ->assertHeader ('Retry-After ' , 1 );
280
+ $ response ->assertHeader ('X-RateLimit-Reset ' , now ()->addSecond ()->timestamp );
281
+ $ response ->assertHeader ('X-RateLimit-Limit ' , 3 );
282
+ $ response ->assertHeader ('X-RateLimit-Remaining ' , 0 );
283
+
284
+ // We will now make it the very end of the second, to check boundaries,
285
+ // and make another request that should be rate limited and tell us to
286
+ // try again in 1 second.
287
+ Carbon::setTestNow ('2000-01-01 00:00:00.999 ' );
288
+
289
+ $ response = $ this ->get ('/ ' );
290
+ $ response ->assertHeader ('Retry-After ' , 1 );
291
+ $ response ->assertHeader ('X-RateLimit-Reset ' , now ()->addSecond ()->timestamp );
292
+ $ response ->assertHeader ('X-RateLimit-Limit ' , 3 );
293
+ $ response ->assertHeader ('X-RateLimit-Remaining ' , 0 );
294
+
295
+ // We now tick over into the next second. We should now be able to make
296
+ // another two requests before the per minute rate limit kicks in.
297
+ Carbon::setTestNow ('2000-01-01 00:00:01.000 ' );
298
+
299
+ for ($ i = 0 ; $ i < 2 ; $ i ++) {
300
+ match ($ i ) {
301
+ 0 => $ this ->assertSame ('2000-01-01 00:00:01.000 ' , now ()->toDateTimeString ('m ' )),
302
+ 1 => $ this ->assertSame ('2000-01-01 00:00:01.100 ' , now ()->toDateTimeString ('m ' )),
303
+ };
304
+
305
+ $ response = $ this ->get ('/ ' );
306
+ $ response ->assertOk ();
307
+ $ response ->assertContent ('ok ' );
308
+
309
+ Carbon::setTestNow (now ()->addMilliseconds (100 ));
310
+ }
311
+
312
+ // The per minute rate limiter should now fail.
313
+
314
+ $ this ->assertSame ('2000-01-01 00:00:01.200 ' , now ()->toDateTimeString ('m ' ));
315
+
316
+ $ response = $ this ->get ('/ ' );
317
+ $ response ->assertStatus (429 );
318
+ $ response ->assertHeader ('Retry-After ' , 59 );
319
+ $ response ->assertHeader ('X-RateLimit-Reset ' , now ()->addSeconds (59 )->timestamp );
320
+ $ response ->assertHeader ('X-RateLimit-Limit ' , 5 );
321
+ $ response ->assertHeader ('X-RateLimit-Remaining ' , 0 );
322
+ }
323
+
245
324
public function testItFailsIfNamedLimiterDoesNotExist ()
246
325
{
247
326
$ this ->expectException (MissingRateLimiterException::class);
0 commit comments