Files

79 lines
2.6 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\RateLimiter;
use Tests\TestCase;
/**
* livewire/update is rate limited (RouteServiceProvider: 'livewire'):
* 120 a minute per IP for guests, 500 a minute per logged-in user.
*
* On 2026-10-08 one bot posted tampered payloads there for 9 hours, up to
* ~850 a minute.
*/
class LivewireRateLimitTest extends TestCase
{
use DatabaseTransactions;
protected function setUp(): void
{
parent::setUp();
// Keep the limiter's counters out of the dev site's Redis (see
// PdfRateLimitTest for why the limiter is rebuilt).
config(['cache.default' => 'array', 'cache.limiter' => 'array']);
$this->app->forgetInstance(\Illuminate\Cache\RateLimiter::class);
RateLimiter::clearResolvedInstances();
$this->app->getProvider(\App\Providers\RouteServiceProvider::class)->configureRateLimiting();
}
private function update(): \Illuminate\Testing\TestResponse
{
// A valid CSRF token, as the bot had: CSRF is checked before the
// limiter, and a request without one is refused (419) uncounted.
// An empty payload: the limit counts requests, not successes.
return $this->withSession(['_token' => 'test-token'])
->withHeader('X-CSRF-TOKEN', 'test-token')
->postJson(route('default.livewire.update'), ['components' => []]);
}
public function test_the_update_route_is_throttled(): void
{
$route = app('router')->getRoutes()->getByName('default.livewire.update');
$this->assertContains('throttle:livewire', $route->gatherMiddleware());
}
public function test_a_guest_is_refused_after_120_a_minute(): void
{
for ($i = 1; $i <= 120; $i++) {
$this->assertNotSame(429, $this->update()->status(), "Request $i is within the limit.");
}
$refused = $this->update();
$refused->assertStatus(429);
$this->assertSame('120', (string) $refused->headers->get('X-RateLimit-Limit'));
}
public function test_a_logged_in_user_gets_500_a_minute_of_their_own(): void
{
$user = User::factory()->create();
// A guest on the same IP has used up the guest allowance...
for ($i = 0; $i < 121; $i++) {
$this->update();
}
$this->update()->assertStatus(429);
// ...which does not touch the user's.
$response = $this->actingAs($user)->update();
$this->assertNotSame(429, $response->status());
$this->assertSame('500', (string) $response->headers->get('X-RateLimit-Limit'));
}
}