Files
Gnuboard7/tests/Feature/Plugin/PluginPermissionGroupTest.php
2026-07-01 10:30:32 +09:00

246 lines
9.6 KiB
PHP

<?php
namespace Tests\Feature\Plugin;
use App\Contracts\Extension\PluginInterface;
use App\Enums\ExtensionOwnerType;
use App\Extension\PluginManager;
use App\Models\Permission;
use App\Models\Role;
use Illuminate\Foundation\Testing\RefreshDatabase;
use ReflectionMethod;
use Tests\TestCase;
/**
* 플러그인 권한 계층 구조 테스트
*
* PluginManager.createPluginPermissions()가 모듈과 동일한
* 3레벨 계층(플러그인 → 카테고리 → 개별 권한)을 생성하는지 검증합니다.
*
* 검증 대상 플러그인: sirsoft-gdpr
* - 그룹 1 (sirsoft-gdpr)
* - 카테고리 1 (privacy)
* - 리프 2 (privacy.view, privacy.update)
*/
class PluginPermissionGroupTest extends TestCase
{
use RefreshDatabase;
/**
* GDPR 플러그인의 ServiceProvider/route/권한 정의를 테스트 앱에 로드.
*
* @var array<string>
*/
protected array $requiredExtensions = [
'plugins/sirsoft-gdpr',
];
private PluginManager $pluginManager;
protected function setUp(): void
{
parent::setUp();
$this->pluginManager = app(PluginManager::class);
$this->pluginManager->loadPlugins();
// 기본 시스템 역할 생성
Role::create([
'identifier' => 'admin',
'name' => ['ko' => '시스템 관리자', 'en' => 'System Administrator'],
'description' => ['ko' => '모든 권한을 가진 관리자', 'en' => 'Administrator with all permissions'],
'extension_type' => ExtensionOwnerType::Core,
'extension_identifier' => 'core',
'is_active' => true,
]);
}
/**
* 검증 대상 GDPR 플러그인 인스턴스를 반환합니다.
*/
private function gdprPlugin(): PluginInterface
{
$plugin = $this->pluginManager->getPlugin('sirsoft-gdpr');
$this->assertNotNull($plugin, 'GDPR 플러그인이 로드되어야 합니다');
return $plugin;
}
/**
* PluginManager의 protected 메서드를 호출하는 헬퍼
*
* @param string $methodName 메서드명
* @param PluginInterface $plugin 대상 플러그인
*/
private function callProtectedMethod(string $methodName, PluginInterface $plugin): void
{
$method = new ReflectionMethod(PluginManager::class, $methodName);
$method->invoke($this->pluginManager, $plugin);
}
/**
* 1레벨: 플러그인 그룹 노드가 생성되는지 확인
*/
public function test_creates_plugin_group_node(): void
{
$this->callProtectedMethod('createPluginPermissions', $this->gdprPlugin());
$groupNode = Permission::where('identifier', 'sirsoft-gdpr')
->whereNull('parent_id')
->first();
$this->assertNotNull($groupNode, '플러그인 그룹 노드가 생성되어야 합니다');
$this->assertEquals(ExtensionOwnerType::Plugin, $groupNode->extension_type);
$this->assertEquals('sirsoft-gdpr', $groupNode->extension_identifier);
$this->assertEquals('GDPR (일반 데이터 보호 규정)', $groupNode->getLocalizedName());
}
/**
* 2레벨: 카테고리 노드가 플러그인 그룹 노드의 자식으로 생성되는지 확인
*/
public function test_creates_category_nodes_under_plugin(): void
{
$this->callProtectedMethod('createPluginPermissions', $this->gdprPlugin());
$groupNode = Permission::where('identifier', 'sirsoft-gdpr')->first();
// 카테고리 노드 확인
$categories = Permission::where('parent_id', $groupNode->id)->orderBy('order')->get();
$this->assertCount(1, $categories, '1개 카테고리가 있어야 합니다');
$this->assertEquals('sirsoft-gdpr.privacy', $categories[0]->identifier);
$this->assertEquals('개인정보 보호', $categories[0]->getLocalizedName());
}
/**
* 3레벨: 개별 권한이 카테고리 노드의 자식으로 등록되는지 확인
*/
public function test_permissions_are_children_of_category_nodes(): void
{
$this->callProtectedMethod('createPluginPermissions', $this->gdprPlugin());
// privacy 카테고리의 자식 권한
$privacyCategory = Permission::where('identifier', 'sirsoft-gdpr.privacy')->first();
$privacyPerms = Permission::where('parent_id', $privacyCategory->id)->orderBy('order')->get();
$this->assertCount(2, $privacyPerms);
$this->assertEquals('sirsoft-gdpr.privacy.view', $privacyPerms[0]->identifier);
$this->assertEquals('sirsoft-gdpr.privacy.update', $privacyPerms[1]->identifier);
}
/**
* 리프 노드(개별 권한)만 할당 가능한지 확인
*/
public function test_only_leaf_permissions_are_assignable(): void
{
$this->callProtectedMethod('createPluginPermissions', $this->gdprPlugin());
// 그룹 노드: 자식 있음 → 할당 불가
$groupNode = Permission::where('identifier', 'sirsoft-gdpr')->first();
$this->assertTrue($groupNode->children()->exists());
// 카테고리 노드: 자식 있음 → 할당 불가
$privacyCategory = Permission::where('identifier', 'sirsoft-gdpr.privacy')->first();
$this->assertTrue($privacyCategory->children()->exists());
// 리프 권한: 자식 없음 → 할당 가능
$leafPerm = Permission::where('identifier', 'sirsoft-gdpr.privacy.view')->first();
$this->assertFalse($leafPerm->children()->exists());
}
/**
* 전체 권한 수 확인 (그룹 1 + 카테고리 1 + 개별 2 = 4)
*/
public function test_total_permission_count(): void
{
$this->callProtectedMethod('createPluginPermissions', $this->gdprPlugin());
$total = Permission::where('extension_identifier', 'sirsoft-gdpr')->count();
$this->assertEquals(4, $total, '그룹 1개 + 카테고리 1개 + 개별 권한 2개 = 4개');
}
/**
* categories가 없는 플러그인은 권한 노드를 생성하지 않는지 확인
*/
public function test_no_permissions_for_plugin_without_categories(): void
{
$mockPlugin = $this->createMock(PluginInterface::class);
$mockPlugin->method('getPermissions')->willReturn([]);
$mockPlugin->method('getIdentifier')->willReturn('test-no-permissions');
$method = new ReflectionMethod(PluginManager::class, 'createPluginPermissions');
$method->invoke($this->pluginManager, $mockPlugin);
$this->assertEquals(0, Permission::where('extension_identifier', 'test-no-permissions')->count());
}
/**
* 재실행(syncPermission) 시 중복 생성 없이 업데이트되는지 확인
*/
public function test_sync_updates_without_duplication(): void
{
$plugin = $this->gdprPlugin();
// 1차 실행
$this->callProtectedMethod('createPluginPermissions', $plugin);
$firstCount = Permission::where('extension_identifier', 'sirsoft-gdpr')->count();
$firstGroupId = Permission::where('identifier', 'sirsoft-gdpr')->first()->id;
// 2차 실행 (업데이트 시뮬레이션)
$this->callProtectedMethod('createPluginPermissions', $plugin);
$secondCount = Permission::where('extension_identifier', 'sirsoft-gdpr')->count();
$secondGroupId = Permission::where('identifier', 'sirsoft-gdpr')->first()->id;
$this->assertEquals($firstCount, $secondCount, '중복 생성 없이 동일 수');
$this->assertEquals($firstGroupId, $secondGroupId, '그룹 노드 ID 유지');
$this->assertEquals(4, $secondCount);
}
/**
* 플러그인 삭제 시 모든 권한(그룹+카테고리+개별)이 삭제되는지 확인
*/
public function test_remove_deletes_all_permission_levels(): void
{
$plugin = $this->gdprPlugin();
$this->callProtectedMethod('createPluginPermissions', $plugin);
$this->assertEquals(4, Permission::where('extension_identifier', 'sirsoft-gdpr')->count());
$this->callProtectedMethod('removePluginPermissions', $plugin);
$this->assertEquals(0, Permission::where('extension_identifier', 'sirsoft-gdpr')->count());
}
/**
* 역할 할당이 카테고리 구조에서 올바르게 동작하는지 확인
*/
public function test_assign_permissions_to_roles_with_categories(): void
{
$plugin = $this->gdprPlugin();
// 플러그인 역할 생성
Role::create([
'identifier' => 'sirsoft-gdpr.privacy',
'name' => ['ko' => '개인정보 운영자', 'en' => 'Privacy Operator'],
'description' => ['ko' => '개인정보 운영', 'en' => 'Privacy operation'],
'extension_type' => ExtensionOwnerType::Plugin,
'extension_identifier' => 'sirsoft-gdpr',
'is_active' => true,
]);
// 권한 생성
$this->callProtectedMethod('createPluginPermissions', $plugin);
// 역할 할당
$this->callProtectedMethod('assignPermissionsToRoles', $plugin);
// admin 역할에 2개 리프 권한 할당 확인
$adminRole = Role::where('identifier', 'admin')->first();
$assignedPerms = $adminRole->permissions()->where('extension_identifier', 'sirsoft-gdpr')->get();
$this->assertCount(2, $assignedPerms, 'admin 역할에 2개 리프 권한이 할당되어야 합니다');
// 그룹/카테고리 노드는 역할에 할당되지 않음
$assignedIdentifiers = $assignedPerms->pluck('identifier')->toArray();
$this->assertNotContains('sirsoft-gdpr', $assignedIdentifiers);
$this->assertNotContains('sirsoft-gdpr.privacy', $assignedIdentifiers);
}
}