Files
Gnuboard7/tests/Feature/AuthorizationTest.php
2026-04-01 10:30:52 +09:00

406 lines
14 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Permission;
use App\Models\Role;
use App\Models\Template;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class AuthorizationTest extends TestCase
{
use RefreshDatabase;
protected User $adminUser;
protected User $regularUser;
protected Role $adminRole;
protected Role $userRole;
protected function setUp(): void
{
parent::setUp();
// 역할 생성
$this->adminRole = Role::create([
'identifier' => 'admin',
'name' => [
'ko' => '관리자',
'en' => 'Administrator',
],
'description' => [
'ko' => '시스템 관리자',
'en' => 'System Administrator',
],
]);
$this->userRole = Role::create([
'identifier' => 'user',
'name' => [
'ko' => '일반 사용자',
'en' => 'Regular User',
],
'description' => [
'ko' => '일반 사용자',
'en' => 'Regular User',
],
]);
// 권한 생성 (실제 라우트에서 사용하는 권한 식별자에 맞춤)
$permissions = [
[
'identifier' => 'core.templates.view',
'name' => [
'ko' => '템플릿 조회',
'en' => 'View Templates',
],
'description' => [
'ko' => '템플릿 목록 및 상세 정보를 조회할 수 있습니다.',
'en' => 'Can view template list and details.',
],
],
[
'identifier' => 'core.templates.edit',
'name' => [
'ko' => '템플릿 편집',
'en' => 'Edit Templates',
],
'description' => [
'ko' => '템플릿을 수정할 수 있습니다.',
'en' => 'Can edit templates.',
],
],
[
'identifier' => 'core.templates.read',
'name' => [
'ko' => '템플릿 읽기',
'en' => 'Read Templates',
],
'description' => [
'ko' => '템플릿을 읽을 수 있습니다.',
'en' => 'Can read templates.',
],
],
[
'identifier' => 'core.templates.activate',
'name' => [
'ko' => '템플릿 활성화',
'en' => 'Activate Templates',
],
'description' => [
'ko' => '템플릿을 활성화/비활성화할 수 있습니다.',
'en' => 'Can activate/deactivate templates.',
],
],
[
'identifier' => 'core.templates.layouts.view',
'name' => [
'ko' => '레이아웃 조회',
'en' => 'View Layouts',
],
'description' => [
'ko' => '레이아웃을 조회할 수 있습니다.',
'en' => 'Can view layouts.',
],
],
[
'identifier' => 'core.templates.layouts.edit',
'name' => [
'ko' => '레이아웃 편집',
'en' => 'Edit Layouts',
],
'description' => [
'ko' => '레이아웃을 편집할 수 있습니다.',
'en' => 'Can edit layouts.',
],
],
];
foreach ($permissions as $permissionData) {
$permission = Permission::create($permissionData);
// 관리자 역할에만 권한 부여
$this->adminRole->permissions()->attach($permission->id);
}
// 사용자 생성
$this->adminUser = User::factory()->create([
'email' => 'admin@example.com',
'name' => 'Admin User',
]);
$this->regularUser = User::factory()->create([
'email' => 'user@example.com',
'name' => 'Regular User',
]);
// 역할 할당
$this->adminUser->roles()->attach($this->adminRole->id);
$this->regularUser->roles()->attach($this->userRole->id);
// 테스트용 템플릿 생성 - 고유 identifier 사용하여 트랜잭션 락 충돌 방지
Template::create([
'identifier' => 'test-template-' . uniqid(),
'vendor' => 'test',
'name' => [
'ko' => '테스트 템플릿',
'en' => 'Test Template',
],
'version' => '1.0.0',
'type' => 'admin',
'description' => [
'ko' => '테스트용 템플릿',
'en' => 'Test template',
],
'status' => 'active',
]);
}
/**
* 권한이 permissions 테이블에 올바르게 등록되었는지 확인
*/
public function test_permissions_are_registered_correctly(): void
{
$this->assertDatabaseHas('permissions', [
'identifier' => 'core.templates.view',
]);
$this->assertDatabaseHas('permissions', [
'identifier' => 'core.templates.edit',
]);
$this->assertDatabaseHas('permissions', [
'identifier' => 'core.templates.layouts.view',
]);
$this->assertDatabaseHas('permissions', [
'identifier' => 'core.templates.layouts.edit',
]);
// 권한이 다국어 구조로 저장되었는지 확인
$permission = Permission::where('identifier', 'core.templates.view')->first();
$this->assertIsArray($permission->name);
$this->assertEquals('템플릿 조회', $permission->name['ko']);
$this->assertEquals('View Templates', $permission->name['en']);
}
/**
* 관리자 사용자가 core.templates.view, core.templates.edit 권한을 가지고 있는지 확인
*/
public function test_admin_user_has_template_permissions(): void
{
$this->assertTrue($this->adminUser->hasPermission('core.templates.view'));
$this->assertTrue($this->adminUser->hasPermission('core.templates.edit'));
$this->assertTrue($this->adminUser->hasPermission('core.templates.layouts.view'));
$this->assertTrue($this->adminUser->hasPermission('core.templates.layouts.edit'));
}
/**
* 일반 사용자로 템플릿 API 접근 시 403 응답 확인 (미들웨어에서 차단)
*/
public function test_regular_user_receives_403_when_accessing_template_api(): void
{
Sanctum::actingAs($this->regularUser);
$response = $this->getJson(route('api.admin.templates.index'));
$response->assertStatus(403);
}
/**
* 토큰 없이 API 호출 시 401 응답 확인
*/
public function test_unauthenticated_request_receives_401(): void
{
$response = $this->getJson(route('api.admin.templates.index'));
$response->assertStatus(401);
}
/**
* 만료된 토큰으로 API 호출 시 401 응답 확인
*/
public function test_expired_token_receives_401(): void
{
// 토큰 생성
$token = $this->adminUser->createToken('test-token')->plainTextToken;
// 토큰을 직접 삭제하여 만료된 상태 시뮬레이션
$this->adminUser->tokens()->delete();
$response = $this->getJson(route('api.admin.templates.index'), [
'Authorization' => 'Bearer '.$token,
]);
$response->assertStatus(401);
}
/**
* core.templates.activate 권한이 올바르게 동작하는지 확인
*
* 라우트: PUT /api/admin/templates/{templateName}/layouts/{name}
* 미들웨어: permission:core.templates.activate
*/
public function test_templates_layouts_edit_permission_works_correctly(): void
{
// 관리자는 토큰 기반 인증으로 접근 가능
$adminToken = $this->adminUser->createToken('test-token')->plainTextToken;
$response = $this->withHeaders([
'Authorization' => 'Bearer '.$adminToken,
'Accept' => 'application/json',
])->putJson(route('api.admin.templates.layouts.update', [
'templateName' => 'test-template',
'name' => 'test-layout',
]), [
'content' => [
'version' => '1.0.0',
'layout_name' => 'test-layout',
'endpoint' => '/api/admin/test',
'components' => [],
],
]);
// 404 (레이아웃이 없음), 200, 또는 422(검증 실패)이면 권한 체크는 통과한 것
// 403이 아니면 admin/permission 미들웨어를 통과한 것으로 간주
$this->assertNotEquals(403, $response->status(), '관리자 요청이 권한 체크에서 실패했습니다.');
// 일반 사용자는 접근 불가
// 일반 사용자는 admin 역할이 없으므로 admin 미들웨어에서 403 반환되어야 함
// 그러나 FormRequest 검증이 미들웨어보다 먼저 실행될 수 있음
// 어느 쪽이든 성공(200)이 아니면 테스트 통과
$regularToken = $this->regularUser->createToken('test-token')->plainTextToken;
$response = $this->withHeaders([
'Authorization' => 'Bearer '.$regularToken,
'Accept' => 'application/json',
])->putJson(route('api.admin.templates.layouts.update', [
'templateName' => 'test-template',
'name' => 'test-layout',
]), [
'content' => [
'version' => '1.0.0',
'layout_name' => 'test-layout',
'endpoint' => '/api/admin/test',
'components' => [],
],
]);
// 일반 사용자가 성공적으로 레이아웃을 수정할 수 없어야 함
// 403(권한 없음), 422(검증 실패) 모두 허용
$this->assertContains($response->status(), [403, 422], '일반 사용자가 레이아웃 수정에 성공해서는 안됩니다.');
}
/**
* API 속도 제한 헤더 존재 확인
*
* api 미들웨어 그룹에 기본 throttle이 적용되어 있으므로
* 헤더 존재 여부만 확인합니다.
*/
public function test_rate_limit_headers_are_present(): void
{
Sanctum::actingAs($this->adminUser);
$response = $this->getJson(route('api.admin.templates.index'));
// Rate Limit 헤더가 존재하는지 확인
$response->assertHeader('X-RateLimit-Limit')
->assertHeader('X-RateLimit-Remaining');
// Rate Limit 값이 적용되었는지 확인 (api 그룹 기본값 또는 라우트 설정값)
$rateLimit = (int) $response->headers->get('X-RateLimit-Limit');
$this->assertGreaterThan(0, $rateLimit);
}
/**
* FormRequest의 authorize()가 항상 true를 반환하는지 확인
*/
public function test_form_request_authorize_always_returns_true(): void
{
// UpdateLayoutRequest 클래스를 직접 테스트
$request = new \App\Http\Requests\Layout\UpdateLayoutRequest();
$request->setUserResolver(fn () => $this->regularUser);
$this->assertTrue($request->authorize());
// 관리자 사용자로도 테스트
$request->setUserResolver(fn () => $this->adminUser);
$this->assertTrue($request->authorize());
}
/**
* Controller에 권한 체크 로직이 없는지 확인
*/
public function test_controller_has_no_permission_check_logic(): void
{
// TemplateController 소스 코드를 읽어서 확인
$controllerPath = app_path('Http/Controllers/Api/Admin/TemplateController.php');
$controllerContent = file_get_contents($controllerPath);
// authorize(), can(), hasPermission() 등의 권한 체크 메서드가 없는지 확인
$this->assertStringNotContainsString('$this->authorize(', $controllerContent);
$this->assertStringNotContainsString('->can(', $controllerContent);
$this->assertStringNotContainsString('->hasPermission(', $controllerContent);
// LayoutController도 확인
$layoutControllerPath = app_path('Http/Controllers/Api/Admin/LayoutController.php');
$layoutControllerContent = file_get_contents($layoutControllerPath);
$this->assertStringNotContainsString('$this->authorize(', $layoutControllerContent);
$this->assertStringNotContainsString('->can(', $layoutControllerContent);
$this->assertStringNotContainsString('->hasPermission(', $layoutControllerContent);
}
/**
* 관리자가 템플릿 목록을 조회할 수 있는지 확인
*/
public function test_admin_can_view_templates(): void
{
Sanctum::actingAs($this->adminUser);
$response = $this->getJson(route('api.admin.templates.index'));
$response->assertStatus(200);
}
/**
* 관리자가 템플릿 상세를 조회할 수 있는지 확인
*
* 템플릿 디렉토리가 존재하지 않으면 404를 반환할 수 있습니다.
*/
public function test_admin_can_view_template_details(): void
{
Sanctum::actingAs($this->adminUser);
$template = Template::first();
$response = $this->getJson(route('api.admin.templates.show', ['templateName' => $template->identifier]));
// 200 (템플릿 디렉토리 존재) 또는 404 (템플릿 디렉토리 없음) 허용
// 권한 체크가 통과했으므로 403이 아니면 성공
$this->assertContains($response->status(), [200, 404]);
$this->assertNotEquals(403, $response->status());
}
/**
* 일반 사용자가 템플릿 활성화를 시도할 때 403 응답 확인
*/
public function test_regular_user_cannot_activate_templates(): void
{
Sanctum::actingAs($this->regularUser);
$template = Template::first();
$response = $this->postJson(route('api.admin.templates.activate'), [
'identifier' => $template->identifier,
]);
$response->assertStatus(403);
}
}