Files
Gnuboard7/tests/Feature/Api/Admin/ResourceAbilitiesTest.php
T
2026-05-11 11:29:41 +09:00

263 lines
9.8 KiB
PHP

<?php
namespace Tests\Feature\Api\Admin;
use App\Enums\ExtensionOwnerType;
use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* API 리소스의 abilities 메타 통합 테스트
*
* BaseApiResource.resourceMeta()가 실제 API 응답에서
* abilities 키를 올바르게 반환하는지 검증합니다.
*/
class ResourceAbilitiesTest extends TestCase
{
use RefreshDatabase;
/**
* sirsoft-marketing 플러그인이 User 리소스 훅에 개입하므로 마이그레이션 필요
*/
protected array $requiredExtensions = [
'plugins/sirsoft-marketing',
];
private User $adminUser;
private string $adminToken;
protected function setUp(): void
{
parent::setUp();
$this->seed();
// Admin 사용자 생성
$this->adminUser = $this->createUserWithRole([
'core.users.read',
'core.users.create',
'core.users.update',
'core.users.delete',
]);
$this->adminToken = $this->adminUser->createToken('test')->plainTextToken;
}
// =========================================================================
// Admin 사용자 abilities 테스트
// =========================================================================
/**
* Admin 역할 사용자는 모든 abilities가 true로 반환된다.
*/
public function test_admin_user_gets_all_abilities_true(): void
{
$targetUser = User::factory()->create();
$response = $this->withToken($this->adminToken)
->getJson("/api/admin/users/{$targetUser->uuid}");
$response->assertOk();
$data = $response->json('data');
$this->assertArrayHasKey('abilities', $data);
$this->assertTrue($data['abilities']['can_read']);
$this->assertTrue($data['abilities']['can_create']);
$this->assertTrue($data['abilities']['can_update']);
$this->assertTrue($data['abilities']['can_delete']);
}
/**
* abilities 키는 abilityMap 정의 개수만큼의 항목을 포함한다.
*/
public function test_abilities_contains_expected_keys(): void
{
$targetUser = User::factory()->create();
$response = $this->withToken($this->adminToken)
->getJson("/api/admin/users/{$targetUser->uuid}");
$response->assertOk();
$data = $response->json('data');
$this->assertArrayHasKey('abilities', $data);
// UserResource의 abilityMap에는 can_read, can_create, can_update, can_delete, can_assign_roles 5개
$this->assertCount(5, $data['abilities']);
$this->assertArrayHasKey('can_read', $data['abilities']);
$this->assertArrayHasKey('can_create', $data['abilities']);
$this->assertArrayHasKey('can_update', $data['abilities']);
$this->assertArrayHasKey('can_delete', $data['abilities']);
$this->assertArrayHasKey('can_assign_roles', $data['abilities']);
}
// =========================================================================
// permission 기반 abilities 테스트 (scope_type 시스템 도입 이후)
// =========================================================================
/**
* 자기 자신 조회 시에도 core.users.update 권한이 없으면 can_update는 false로 반환된다.
*
* 과거 abilityBypassMap() 의 self/owner 바이패스는 폐기되고 scope_type 시스템으로
* 대체되었으므로, permission 보유 여부가 단일 판정 기준이 된다.
*/
public function test_self_user_gets_can_update_false_without_permission(): void
{
// core.users.read + core.users.create만 가진 사용자 (update 없음)
$user = $this->createUserWithRole([
'core.users.read',
'core.users.create',
]);
$token = $user->createToken('test')->plainTextToken;
// 자기 자신 조회
$response = $this->withToken($token)
->getJson("/api/admin/users/{$user->uuid}");
$response->assertOk();
$abilities = $response->json('data.abilities');
$this->assertFalse($abilities['can_update'], 'permission 없으면 자기 자신이라도 can_update=false');
$this->assertFalse($abilities['can_delete'], 'permission 없으면 can_delete=false');
}
/**
* 다른 사용자 조회 시 core.users.update 권한 없으면 can_update가 false로 반환된다.
*/
public function test_other_user_gets_can_update_false_without_permission(): void
{
// core.users.read만 가진 사용자 (update 없음)
$user = $this->createUserWithRole([
'core.users.read',
]);
$token = $user->createToken('test')->plainTextToken;
$otherUser = User::factory()->create();
// 다른 사용자 조회
$response = $this->withToken($token)
->getJson("/api/admin/users/{$otherUser->uuid}");
$response->assertOk();
$abilities = $response->json('data.abilities');
$this->assertFalse($abilities['can_update'], '타인은 can_update가 false여야 합니다');
}
// =========================================================================
// is_owner 테스트 (UserResource ownerField='id')
// =========================================================================
/**
* UserResource 는 ownerField='id' 를 정의하므로 is_owner 키가 응답에 포함된다.
*
* 자기 자신 조회 시 is_owner=true, 타인 조회 시 is_owner=false.
* (ownerField 미정의 리소스의 is_owner 생략 동작은 BaseApiResource 단위 테스트에서 검증.)
*/
public function test_user_resource_includes_is_owner_based_on_request_user(): void
{
$targetUser = User::factory()->create();
// 타인 조회: is_owner=false
$responseOther = $this->withToken($this->adminToken)
->getJson("/api/admin/users/{$targetUser->uuid}");
$responseOther->assertOk();
$this->assertArrayHasKey('is_owner', $responseOther->json('data'));
$this->assertFalse($responseOther->json('data.is_owner'));
// 자기 자신 조회: is_owner=true
$responseSelf = $this->withToken($this->adminToken)
->getJson("/api/admin/users/{$this->adminUser->uuid}");
$responseSelf->assertOk();
$this->assertTrue($responseSelf->json('data.is_owner'));
}
// =========================================================================
// 기존 permissions 키 유지 확인
// =========================================================================
/**
* UserResource의 기존 permissions 키(역할 권한 목록)가 정상 유지된다.
*/
public function test_existing_permissions_key_preserved_in_user_detail(): void
{
$response = $this->withToken($this->adminToken)
->getJson("/api/admin/users/{$this->adminUser->uuid}?detail=true");
$response->assertOk();
$data = $response->json('data');
// abilities 키와 permissions 키가 공존
$this->assertArrayHasKey('abilities', $data);
// permissions 키는 기존 역할 권한 목록 (abilities와 다른 용도)
// 상세 조회 시에만 포함 (whenLoaded)
}
// =========================================================================
// abilityMap 미정의 리소스 테스트
// =========================================================================
/**
* abilityMap이 빈 리소스는 abilities 키가 포함되지 않는다.
*/
public function test_resource_without_ability_map_excludes_abilities_key(): void
{
// ActivityLogResource는 abilityMap 미정의
// 직접 API 호출 대신 단위 테스트에서 이미 검증
// 여기서는 구조적 검증만 수행
$this->assertTrue(true, 'abilityMap 미정의 리소스는 단위 테스트에서 검증됨');
}
// =========================================================================
// 헬퍼 메서드
// =========================================================================
/**
* 특정 역할과 권한을 가진 사용자를 생성합니다.
*
* @param array $permissions 권한 식별자 목록
* @return User 생성된 사용자
*/
private function createUserWithRole(array $permissions = []): User
{
static $roleCounter = 0;
$roleCounter++;
$user = User::factory()->create();
// 테스트별 고유 역할 생성 (권한 격리를 위해)
$role = Role::create([
'identifier' => 'test_admin_' . $roleCounter . '_' . uniqid(),
'name' => json_encode(['ko' => '테스트 관리자', 'en' => 'Test Admin']),
'description' => json_encode(['ko' => '테스트용', 'en' => 'For testing']),
'extension_type' => ExtensionOwnerType::Core,
'extension_identifier' => 'core',
'is_active' => true,
]);
// 권한 생성 및 역할에 연결
$permissionIds = [];
foreach ($permissions as $identifier) {
$permission = Permission::firstOrCreate(
['identifier' => $identifier],
[
'name' => json_encode(['ko' => $identifier, 'en' => $identifier]),
'description' => json_encode(['ko' => $identifier, 'en' => $identifier]),
'extension_type' => ExtensionOwnerType::Core,
'extension_identifier' => 'core',
'type' => 'admin',
]
);
$permissionIds[] = $permission->id;
}
$role->permissions()->sync($permissionIds);
// 역할 할당
$user->roles()->attach($role->id, ['assigned_at' => now()]);
return $user->fresh();
}
}