clearPermissionCache(); // 스코프가 있는 권한 (메뉴 관리) $this->permission = Permission::create([ 'identifier' => 'test.menus.update', 'name' => ['ko' => '메뉴 수정', 'en' => 'Update Menu'], 'type' => PermissionType::Admin, 'resource_route_key' => 'menu', 'owner_key' => 'created_by', ]); // 스코프가 없는 권한 (시스템 리소스) $this->noScopePermission = Permission::create([ 'identifier' => 'test.settings.read', 'name' => ['ko' => '설정 조회', 'en' => 'Read Settings'], 'type' => PermissionType::Admin, 'resource_route_key' => null, 'owner_key' => null, ]); // 사용자 생성 $this->ownerUser = User::factory()->create(); $this->otherUser = User::factory()->create(); $this->sharedRoleUser = User::factory()->create(); // 역할 생성 및 할당 $role = Role::create([ 'identifier' => 'test_manager', 'name' => ['ko' => '매니저', 'en' => 'Manager'], 'is_active' => true, ]); $role->permissions()->attach($this->permission->id, ['scope_type' => 'self']); $role->permissions()->attach($this->noScopePermission->id, ['scope_type' => null]); $this->ownerUser->roles()->attach($role->id); $this->sharedRoleUser->roles()->attach($role->id); } protected function tearDown(): void { $this->clearPermissionCache(); parent::tearDown(); } // ======================================================================== // checkScopeAccess 테스트 // ======================================================================== /** * scope=null, 타인 리소스 → true (전체 접근) */ public function test_check_scope_access_null_scope_allows_all(): void { // null scope 역할 생성 $role = Role::create([ 'identifier' => 'test_admin_full', 'name' => ['ko' => '전체 접근', 'en' => 'Full Access'], 'is_active' => true, ]); $role->permissions()->attach($this->permission->id, ['scope_type' => null]); $user = User::factory()->create(); $user->roles()->attach($role->id); $menu = $this->createMenu(['created_by' => $this->otherUser->id]); $this->assertTrue(PermissionHelper::checkScopeAccess($menu, 'test.menus.update', $user)); } /** * scope='self', 자기 리소스 → true */ public function test_check_scope_access_self_allows_own_resource(): void { $menu = $this->createMenu(['created_by' => $this->ownerUser->id]); $this->assertTrue(PermissionHelper::checkScopeAccess($menu, 'test.menus.update', $this->ownerUser)); } /** * scope='self', 타인 리소스 → false */ public function test_check_scope_access_self_denies_other_resource(): void { $menu = $this->createMenu(['created_by' => $this->otherUser->id]); $this->assertFalse(PermissionHelper::checkScopeAccess($menu, 'test.menus.update', $this->ownerUser)); } /** * scope='role', 동일 역할 사용자의 리소스 → true */ public function test_check_scope_access_role_allows_shared_role_resource(): void { // role scope 역할 $role = Role::create([ 'identifier' => 'test_role_scope', 'name' => ['ko' => '역할 스코프', 'en' => 'Role Scope'], 'is_active' => true, ]); $role->permissions()->attach($this->permission->id, ['scope_type' => 'role']); $userA = User::factory()->create(); $userB = User::factory()->create(); $userA->roles()->attach($role->id); $userB->roles()->attach($role->id); $menu = $this->createMenu(['created_by' => $userB->id]); $this->assertTrue(PermissionHelper::checkScopeAccess($menu, 'test.menus.update', $userA)); } /** * scope='role', 다른 역할 사용자의 리소스 → false */ public function test_check_scope_access_role_denies_different_role_resource(): void { $role = Role::create([ 'identifier' => 'test_role_scope2', 'name' => ['ko' => '역할 스코프2', 'en' => 'Role Scope2'], 'is_active' => true, ]); $role->permissions()->attach($this->permission->id, ['scope_type' => 'role']); $userA = User::factory()->create(); $userA->roles()->attach($role->id); // otherUser has no role $menu = $this->createMenu(['created_by' => $this->otherUser->id]); $this->assertFalse(PermissionHelper::checkScopeAccess($menu, 'test.menus.update', $userA)); } /** * resource_route_key=null → true (스코프 체크 스킵) */ public function test_check_scope_access_skips_when_no_resource_route_key(): void { $menu = $this->createMenu(['created_by' => $this->otherUser->id]); $this->assertTrue(PermissionHelper::checkScopeAccess($menu, 'test.settings.read', $this->ownerUser)); } /** * 비인증 사용자 → true (스코프 체크 스킵) */ public function test_check_scope_access_skips_for_unauthenticated_user(): void { $menu = $this->createMenu(['created_by' => $this->otherUser->id]); $this->assertTrue(PermissionHelper::checkScopeAccess($menu, 'test.menus.update', null)); } // ======================================================================== // applyPermissionScope 테스트 // ======================================================================== /** * scope=null → 쿼리 변경 없음 */ public function test_apply_scope_null_does_not_modify_query(): void { $role = Role::create([ 'identifier' => 'test_full_access', 'name' => ['ko' => '전체', 'en' => 'Full'], 'is_active' => true, ]); $role->permissions()->attach($this->permission->id, ['scope_type' => null]); $user = User::factory()->create(); $user->roles()->attach($role->id); // 메뉴 3개 생성 $this->createMenu(['created_by' => $user->id]); $this->createMenu(['created_by' => $this->otherUser->id]); $this->createMenu(['created_by' => $this->ownerUser->id]); $query = Menu::query(); PermissionHelper::applyPermissionScope($query, 'test.menus.update', $user); $this->assertSame(3, $query->count()); } /** * scope='self' → WHERE owner_key = user_id 추가 */ public function test_apply_scope_self_filters_own_resources(): void { $this->createMenu(['created_by' => $this->ownerUser->id]); $this->createMenu(['created_by' => $this->otherUser->id]); $query = Menu::query(); PermissionHelper::applyPermissionScope($query, 'test.menus.update', $this->ownerUser); $this->assertSame(1, $query->count()); } /** * owner_key=null → 쿼리 변경 없음 */ public function test_apply_scope_no_owner_key_does_not_modify_query(): void { $this->createMenu(['created_by' => $this->ownerUser->id]); $this->createMenu(['created_by' => $this->otherUser->id]); $query = Menu::query(); PermissionHelper::applyPermissionScope($query, 'test.settings.read', $this->ownerUser); $this->assertSame(2, $query->count()); } /** * 비인증 사용자 → 쿼리 변경 없음 */ public function test_apply_scope_unauthenticated_does_not_modify_query(): void { $this->createMenu(['created_by' => $this->ownerUser->id]); $query = Menu::query(); PermissionHelper::applyPermissionScope($query, 'test.menus.update', null); $this->assertSame(1, $query->count()); } // ======================================================================== // 헬퍼 메서드 // ======================================================================== private function createMenu(array $overrides = []): Menu { return Menu::create(array_merge([ 'name' => ['ko' => '테스트 메뉴', 'en' => 'Test Menu'], 'slug' => 'test-menu-' . uniqid(), 'url' => '/admin/test', 'icon' => 'fas fa-cog', 'order' => 1, 'is_active' => true, ], $overrides)); } private function clearPermissionCache(): void { $reflection = new \ReflectionClass(PermissionHelper::class); $prop = $reflection->getProperty('permissionCache'); $prop->setAccessible(true); $prop->setValue(null, []); } }