Files
portal-api/tests/Feature/Api/Admin/PermissionGatingTest.php
T
baodev0andClaude Opus 5.5 e888a6ed5d feat: Turnstile captcha, menu khách + nút tắt topbar cấu hình từ admin, ẩn setting nhạy cảm
- Turnstile (App\Support\Turnstile, middleware turnstile) cho login admin và login/register/forgot/reset của khách; cấu hình ở setting key=turnstile, secret mã hoá SignRSA.
- Menu sidebar khách (setting client_menu) và nút tắt topbar (setting client_quick_links) do admin sửa qua administrator/client-menu[/quick-links], khách đọc v1/setting/menu, v1/setting/quick-links.
- v1/setting và settings/public chỉ trả whitelist PublicSetting::FIELDS (trước đây lộ bot_token, inet_api_key).
- Cài đặt tài khoản khách (preferences), rate limit theo IP cho register/resend-verify.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:53:43 +07:00

115 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Api\Admin;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class PermissionGatingTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// CheckPermission throws when the checked permission row doesn't exist at all —
// seed every permission this test touches so unauthorized checks 403 instead of 500.
foreach (self::gatedRoutesProvider() as [, , $permission]) {
Permission::firstOrCreate(['name' => $permission, 'guard_name' => 'admin']);
}
Permission::firstOrCreate(['name' => 'some-other.manager', 'guard_name' => 'admin']);
}
private function makeAdminWithPermissions(array $permissions): User
{
$role = Role::create(['name' => 'role-' . uniqid(), 'guard_name' => 'admin']);
foreach ($permissions as $permission) {
$role->givePermissionTo(
Permission::firstOrCreate(['name' => $permission, 'guard_name' => 'admin']),
);
}
$admin = User::factory()->create();
$admin->assignRole($role);
return $admin;
}
/** @dataProvider gatedRoutesProvider */
public function test_route_requires_expected_permission(string $method, string $uri, string $requiredPermission): void
{
$unauthorized = $this->makeAdminWithPermissions(['some-other.manager']);
$this->actingAs($unauthorized, 'admin')->json($method, $uri)->assertStatus(403);
$authorized = $this->makeAdminWithPermissions([$requiredPermission]);
$response = $this->actingAs($authorized, 'admin')->json($method, $uri);
$this->assertNotSame(403, $response->status(), "Expected {$uri} to be accessible with '{$requiredPermission}', got 403.");
}
public static function gatedRoutesProvider(): array
{
return [
'settings index' => ['GET', '/api/v1/administrator/settings', 'setting.manager'],
'client-menu index' => ['GET', '/api/v1/administrator/client-menu', 'setting.manager'],
'banks index' => ['GET', '/api/v1/administrator/banks', 'setting.manager'],
'domain-pricing index' => ['GET', '/api/v1/administrator/domain-pricing', 'setting.manager'],
'email-templates index' => ['GET', '/api/v1/administrator/email-templates', 'email-template.manager'],
'tickets index' => ['GET', '/api/v1/administrator/tickets', 'support.view'],
'activity-logs index' => ['GET', '/api/v1/administrator/activity-logs', 'system-log.manager'],
'email-logs index' => ['GET', '/api/v1/administrator/email-logs', 'system-log.manager'],
'post-categories index' => ['GET', '/api/v1/administrator/post-categories', 'post.view'],
'posts index' => ['GET', '/api/v1/administrator/posts', 'post.view'],
'media index' => ['GET', '/api/v1/administrator/media', 'media.view'],
'dashboard stats' => ['GET', '/api/v1/administrator/dashboard/stats', 'dashboard.view'],
'queue jobs recent' => ['GET', '/api/v1/administrator/queue/jobs/recent', 'queue.manager'],
];
}
public function test_force_delete_post_requires_post_destroy_permission(): void
{
Permission::firstOrCreate(['name' => 'post.destroy', 'guard_name' => 'admin']);
$unauthorized = $this->makeAdminWithPermissions(['post.view']);
$this->actingAs($unauthorized, 'admin')
->deleteJson('/api/v1/administrator/posts/999/force-delete')
->assertStatus(403);
$authorized = $this->makeAdminWithPermissions(['post.destroy']);
$response = $this->actingAs($authorized, 'admin')
->deleteJson('/api/v1/administrator/posts/999/force-delete');
$this->assertNotSame(403, $response->status());
}
public function test_dashboard_requires_dashboard_view_permission(): void
{
Permission::firstOrCreate(['name' => 'dashboard.view', 'guard_name' => 'admin']);
$unauthorized = $this->makeAdminWithPermissions(['post.view']);
$this->actingAs($unauthorized, 'admin')
->getJson('/api/v1/administrator/dashboard/stats')
->assertStatus(403);
$authorized = $this->makeAdminWithPermissions(['dashboard.view']);
$response = $this->actingAs($authorized, 'admin')->getJson('/api/v1/administrator/dashboard/stats');
$this->assertNotSame(403, $response->status());
}
public function test_full_administrator_bypasses_all_permission_gates(): void
{
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
$admin = User::factory()->create();
$admin->assignRole('Full Administrator');
foreach (self::gatedRoutesProvider() as [$method, $uri]) {
$response = $this->actingAs($admin, 'admin')->json($method, $uri);
$this->assertNotSame(403, $response->status(), "Full Administrator should not be blocked from {$uri}.");
}
}
}