'array', 'cache.limiter' => 'array']); // The limiter was built at boot, on the Redis store, before the line // above ran: rebuild it, or its counters still land in the dev cache // (and survive into the next run). Its named limits live on the // instance, so they are registered again - without them 'pdf' reads // as a limit of 0 and every second request is refused. $this->app->forgetInstance(\Illuminate\Cache\RateLimiter::class); RateLimiter::clearResolvedInstances(); $this->app->getProvider(\App\Providers\RouteServiceProvider::class)->configureRateLimiting(); } public function test_the_thirty_first_pdf_in_a_minute_is_refused(): void { // An id that does not exist: the limit counts requests, not // successes, and nothing is rendered. $url = route('call.pdf-a4-bw', ['id' => 999999999]); for ($i = 1; $i <= 30; $i++) { $this->assertNotSame(429, $this->get($url)->status(), "Request $i is within the limit."); } $refused = $this->get($url); $refused->assertStatus(429); $this->assertNotNull($refused->headers->get('Retry-After')); $this->assertSame('30', (string) $refused->headers->get('X-RateLimit-Limit')); } public function test_every_pdf_route_shares_one_allowance(): void { // Switching format must not open a fresh allowance: the crawler // fetched every variant of every call. $routes = ['call.pdf', 'call.pdf-bw', 'call.pdf-a4', 'call.pdf-a4-bw', 'post.pdf']; for ($i = 0; $i < 30; $i++) { $this->get(route($routes[$i % count($routes)], ['id' => 999999999])); } $this->get(route('post.pdf', ['id' => 999999999]))->assertStatus(429); $this->get(route('call.pdf-bulk'))->assertStatus(429); $this->get(route('post.pdf-bulk'))->assertStatus(429); } public function test_other_ips_keep_their_own_allowance(): void { $url = route('post.pdf', ['id' => 999999999]); for ($i = 0; $i < 31; $i++) { $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.1'])->get($url); } // The first IP is now refused... $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.1'])->get($url)->assertStatus(429); // ...and a second one, behind its own limit, is not. $this->assertNotSame( 429, $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.2'])->get($url)->status() ); } public function test_search_has_its_own_limit_of_thirty_a_minute(): void { $route = app('router')->getRoutes()->getByName('search.show'); $this->assertContains('throttle:search', $route->gatherMiddleware()); $limit = RateLimiter::limiter('search')(request()); $this->assertSame(30, $limit->maxAttempts); } }