thats-me/backend/tests/Feature/Api/LoginThrottleTest.php
Kevin Adametz b262a6771f Backend: Medien-Typen, Sprachnotizen, Event-Felder und Sync-Härtung
- Event-Medien tragen Typ (image/video/audio), Dauer und Notiz; Videos
  bekommen ein Poster, Audio wird per ffmpeg nach AAC (audio/mp4)
  gewandelt, Originale werden mit Range-Unterstützung gestreamt.
- Upload nimmt eine Notiz (caption) direkt mit, PATCH ändert sie später.
- Neue Event-Felder: Zeitraum (end_date), Kategorie, Personen, Meilenstein.
  Regeln zentral in EventFields, damit CRUD und Sync gleich prüfen.
- Titel darf leer sein: ein Event entsteht mit dem ersten Eintrag.
- Artisan-Befehl media:transcode-voice-notes wandelt alte WebM/Ogg-Notizen,
  die Safari nicht abspielen kann.
- Login-Throttling, Settings-Validierung, Sync-Fehlercodes ohne Rohtext.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 07:00:35 +00:00

83 lines
2.7 KiB
PHP

<?php
use App\Models\User;
use Illuminate\Support\Facades\RateLimiter;
use Laravel\Passport\ClientRepository;
beforeEach(function () {
RateLimiter::clear('api-login|throttle@example.com|127.0.0.1');
RateLimiter::clear('api-login|other@example.com|127.0.0.1');
// A successful login issues a personal access token.
app(ClientRepository::class)->createPersonalAccessGrantClient('Test Personal Access Client', 'users');
});
test('repeated failed logins are locked out with http 429', function () {
User::factory()->create([
'email' => 'throttle@example.com',
'password' => 'correct-horse',
]);
// Five wrong passwords are answered with the normal validation error.
foreach (range(1, 5) as $attempt) {
$this->postJson('/api/login', [
'email' => 'throttle@example.com',
'password' => 'wrong',
])->assertStatus(422);
}
// The sixth is refused before the password is ever checked.
$this->postJson('/api/login', [
'email' => 'throttle@example.com',
'password' => 'wrong',
])->assertStatus(429);
// Even the correct password stays locked out during the cooldown.
$this->postJson('/api/login', [
'email' => 'throttle@example.com',
'password' => 'correct-horse',
])->assertStatus(429);
});
test('a successful login clears the failure counter', function () {
User::factory()->create([
'email' => 'throttle@example.com',
'password' => 'correct-horse',
]);
foreach (range(1, 4) as $attempt) {
$this->postJson('/api/login', [
'email' => 'throttle@example.com',
'password' => 'wrong',
])->assertStatus(422);
}
$this->postJson('/api/login', [
'email' => 'throttle@example.com',
'password' => 'correct-horse',
])->assertOk();
// The counter is back to zero, so a fresh run of wrong attempts is
// answered with 422 again rather than an immediate lockout.
$this->postJson('/api/login', [
'email' => 'throttle@example.com',
'password' => 'wrong',
])->assertStatus(422);
});
test('the throttle is scoped per email so one account cannot lock out another', function () {
User::factory()->create(['email' => 'throttle@example.com', 'password' => 'correct-horse']);
User::factory()->create(['email' => 'other@example.com', 'password' => 'correct-horse']);
foreach (range(1, 6) as $attempt) {
$this->postJson('/api/login', [
'email' => 'throttle@example.com',
'password' => 'wrong',
]);
}
$this->postJson('/api/login', [
'email' => 'other@example.com',
'password' => 'correct-horse',
])->assertOk();
});