Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Public PDFs are rate limited per IP (RouteServiceProvider: 'pdf').
|
||||
*
|
||||
* On 2026-10-07 a crawler fetched them ~11 times a second; every one was
|
||||
* rendered from scratch, the server ran out of memory and the kernel killed
|
||||
* MariaDB. A limited request must be refused before any PDF work starts.
|
||||
*/
|
||||
class PdfRateLimitTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Not the Redis store phpunit.xml configures: that is the dev site's
|
||||
// cache, and the limiter's counters would land there.
|
||||
config(['cache.default' => '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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user