new funcionality

This commit is contained in:
2026-07-27 17:20:02 +02:00
parent c2b5291a74
commit c5ce8a7a36
101 changed files with 6889 additions and 176 deletions

View File

@@ -0,0 +1,125 @@
<?php
namespace Tests\Feature;
use App\Models\User;
use Database\Seeders\RolesAndPermissionsSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class RateLimitingTest extends TestCase
{
use RefreshDatabase;
public function test_login_is_throttled_after_five_attempts(): void
{
$user = User::factory()->approved()->create(['password' => bcrypt('correct-password')]);
for ($i = 0; $i < 5; $i++) {
$this->postJson('/web-api/login', [
'email' => $user->email,
'password' => 'wrong-password',
])->assertUnprocessable();
}
$this->postJson('/web-api/login', [
'email' => $user->email,
'password' => 'wrong-password',
])->assertStatus(429);
// El throttle es por (email, ip): otro email desde la misma IP no está afectado.
$other = User::factory()->approved()->create(['password' => bcrypt('correct-password')]);
$this->postJson('/web-api/login', [
'email' => $other->email,
'password' => 'correct-password',
])->assertOk();
}
public function test_register_is_throttled_after_five_attempts(): void
{
// El primer registro de una instalación vacía toma un camino distinto
// (bootstrap admin, 200 en vez de 201, y siembra roles/permisos) — se
// crea un usuario previo y se siembran los roles a mano para que
// todos los intentos del loop pasen por el camino normal.
(new RolesAndPermissionsSeeder)->run();
User::factory()->approved()->create();
for ($i = 0; $i < 5; $i++) {
$this->postJson('/web-api/register', [
'name' => "User {$i}",
'email' => "user{$i}@example.com",
'password' => 'password123',
'password_confirmation' => 'password123',
])->assertCreated();
}
$this->postJson('/web-api/register', [
'name' => 'One Too Many',
'email' => 'onetoomany@example.com',
'password' => 'password123',
'password_confirmation' => 'password123',
])->assertStatus(429);
}
public function test_oauth_token_endpoint_is_throttled_per_client(): void
{
$owner = User::factory()->approved()->create();
$response = $this->actingAs($owner)->postJson('/web-api/clients', [
'name' => 'Throttle Test Client',
'grant_type' => 'client_credentials',
]);
$response->assertCreated();
$clientId = $response->json('client.id');
for ($i = 0; $i < 20; $i++) {
$this->postJson('/oauth/token', [
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => 'wrong-secret',
])->assertStatus(401);
}
$this->postJson('/oauth/token', [
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => 'wrong-secret',
])->assertStatus(429);
}
public function test_oauth_token_throttle_is_scoped_per_client_not_globally(): void
{
$owner = User::factory()->approved()->create();
$clientA = $this->actingAs($owner)->postJson('/web-api/clients', [
'name' => 'Client A',
'grant_type' => 'client_credentials',
])->json('client.id');
for ($i = 0; $i < 20; $i++) {
$this->postJson('/oauth/token', [
'grant_type' => 'client_credentials',
'client_id' => $clientA,
'client_secret' => 'wrong-secret',
]);
}
$this->postJson('/oauth/token', [
'grant_type' => 'client_credentials',
'client_id' => $clientA,
'client_secret' => 'wrong-secret',
])->assertStatus(429);
$clientBResponse = $this->actingAs($owner)->postJson('/web-api/clients', [
'name' => 'Client B',
'grant_type' => 'client_credentials',
]);
$clientBSecret = $clientBResponse->json('plain_secret');
$clientBId = $clientBResponse->json('client.id');
$this->postJson('/oauth/token', [
'grant_type' => 'client_credentials',
'client_id' => $clientBId,
'client_secret' => $clientBSecret,
'scope' => 'users:read',
])->assertOk();
}
}