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); } }