Files
portal-api/tests/Feature/Api/Admin/LoginThrottleTest.php
T

103 lines
3.5 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Api\Admin;
use App\Mail\Admin\AdminLoginAlertMail;
use App\Models\AdminLoginBlock;
use App\Models\Setting;
use App\Models\User;
use App\Support\Auth\AdminLoginAlert;
use Database\Seeders\TemplateSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class LoginThrottleTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
Cache::flush();
Mail::fake();
(new TemplateSeeder())->seedScenarioIfMissing('admin_login_alert');
}
private function login(string $username, string $password)
{
return $this->postJson('/api/v1/administrator/login', ['username' => $username, 'password' => $password]);
}
private function staff(): User
{
return User::factory()->create(['username' => 'staff', 'password' => Hash::make('correct-password-123')]);
}
public function test_ip_is_blocked_for_20_minutes_after_more_than_4_failures(): void
{
$this->staff();
for ($i = 4; $i >= 1; $i--) {
$this->login('staff', 'wrong')->assertUnauthorized()->assertJsonPath('remaining_attempts', $i);
}
$this->login('staff', 'wrong')->assertStatus(429);
$this->login('staff', 'correct-password-123')->assertStatus(429);
$this->login('someone-else', 'whatever')->assertStatus(429);
$this->travel(21)->minutes();
$this->login('staff', 'correct-password-123')->assertOk();
$this->assertSame(0, AdminLoginBlock::count());
}
public function test_successful_login_alerts_configured_recipients(): void
{
$this->staff();
$this->login('staff', 'correct-password-123')->assertOk();
Mail::assertQueued(AdminLoginAlertMail::class, fn ($mail) => $mail->hasTo('web@tgs.com.vn'));
Setting::updateOrCreate(['key' => 'email_settings'], ['value' => json_encode([AdminLoginAlert::SETTING_FIELD => ['it@tgs.com.vn']])]);
$this->login('staff', 'correct-password-123')->assertOk();
Mail::assertQueued(AdminLoginAlertMail::class, fn ($mail) => $mail->hasTo('it@tgs.com.vn'));
}
public function test_empty_recipient_list_disables_alert(): void
{
$this->staff();
Setting::updateOrCreate(['key' => 'email_settings'], ['value' => json_encode([AdminLoginAlert::SETTING_FIELD => []])]);
$this->login('staff', 'correct-password-123')->assertOk();
Mail::assertNothingQueued();
}
public function test_admin_can_list_and_unblock_ip(): void
{
$this->staff();
for ($i = 0; $i < 5; $i++) {
$this->login('staff', 'wrong');
}
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
$admin = User::factory()->create();
$admin->assignRole('Full Administrator');
$id = $this->actingAs($admin, 'admin')->getJson('/api/v1/administrator/login-blocks')
->assertOk()
->assertJsonPath('data.items.0.ip', '127.0.0.1')
->assertJsonPath('data.items.0.last_username', 'staff')
->json('data.items.0.id');
$this->actingAs($admin, 'admin')->deleteJson("/api/v1/administrator/login-blocks/{$id}")->assertOk();
$this->app['auth']->forgetGuards();
$this->login('staff', 'correct-password-123')->assertOk();
}
}