브라우저·SEO 실측(T1~T7 매트릭스 + 봇 렌더 스윕)에서 발견된 결함을 보완한다. - 코어: 회원 일괄 상태변경 응답 메시지의 :count 플레이스홀더 미치환 수정 - 게시판·페이지: 비밀글·삭제글·미발행 첨부 썸네일이 KVE-2026-1914 게이트로 깨진 것을 한시 서명 preview URL(게이트 통과 직렬화 발급 + 유효 서명 허용)로 복구 — 무서명·변조·만료 요청 게이트는 종전과 동일 - 페이지: 발행/미발행 전환(after_publish)에도 SEO 캐시·사이트맵 갱신 구독 - 게시판: 게시판 디렉토리(전체 목록 + 내장 최근글)에 열람 권한 필터 적용 - 검색 화면: searchResults 데이터소스 auth_mode=optional 선언으로 로그인 회원 검색 범위 복구 + data-sources.md 선언 의무 규정화(coverage 등록)
305 lines
9.9 KiB
PHP
305 lines
9.9 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Api\Admin;
|
|
|
|
use App\Enums\ExtensionOwnerType;
|
|
use App\Extension\HookManager;
|
|
use App\Models\Permission;
|
|
use App\Models\Role;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* 사용자 일괄 상태 변경 테스트
|
|
*/
|
|
class UserBulkStatusTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $admin;
|
|
|
|
private string $token;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->admin = $this->createAdminUser();
|
|
$this->token = $this->admin->createToken('test-token')->plainTextToken;
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
// 테스트에서 등록한 Hook 정리
|
|
HookManager::clearAction('sirsoft-core.user.before_bulk_update');
|
|
HookManager::clearAction('sirsoft-core.user.after_bulk_update');
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
/**
|
|
* 관리자 역할 생성 및 할당
|
|
*/
|
|
private function createAdminUser(): User
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
// 관리자 권한 생성
|
|
$permission = Permission::firstOrCreate(
|
|
['identifier' => 'core.users.update'],
|
|
[
|
|
'name' => json_encode(['ko' => '사용자 수정', 'en' => 'Update Users']),
|
|
'description' => json_encode(['ko' => '사용자 정보 수정 권한', 'en' => 'Permission to update users']),
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => 'admin',
|
|
]
|
|
);
|
|
|
|
// 관리자 역할 생성
|
|
$adminRole = Role::firstOrCreate(
|
|
['identifier' => 'admin'],
|
|
[
|
|
'name' => json_encode(['ko' => '관리자', 'en' => 'Administrator']),
|
|
'description' => json_encode(['ko' => '시스템 관리자', 'en' => 'System Administrator']),
|
|
'extension_type' => ExtensionOwnerType::Core,
|
|
'extension_identifier' => 'core',
|
|
'type' => 'admin',
|
|
'is_active' => true,
|
|
]
|
|
);
|
|
|
|
// 역할에 권한 할당
|
|
if (! $adminRole->permissions()->where('permissions.id', $permission->id)->exists()) {
|
|
$adminRole->permissions()->attach($permission->id);
|
|
}
|
|
|
|
// 사용자에게 역할 할당
|
|
$user->roles()->attach($adminRole->id, [
|
|
'assigned_at' => now(),
|
|
'assigned_by' => null,
|
|
]);
|
|
|
|
return $user->fresh();
|
|
}
|
|
|
|
/**
|
|
* 인증된 요청 헬퍼 메서드
|
|
*/
|
|
private function authRequest(): static
|
|
{
|
|
return $this->withHeaders([
|
|
'Authorization' => 'Bearer '.$this->token,
|
|
'Accept' => 'application/json',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 비활성 사용자들을 활성으로 일괄 변경할 수 있다.
|
|
*/
|
|
public function test_bulk_activate_users(): void
|
|
{
|
|
// 비활성 사용자 3명 생성
|
|
$users = User::factory()->count(3)->create(['status' => 'inactive']);
|
|
$ids = $users->pluck('uuid')->toArray();
|
|
|
|
$response = $this->authRequest()->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => $ids,
|
|
'status' => 'active',
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$response->assertJsonStructure([
|
|
'success',
|
|
'message',
|
|
'data' => [
|
|
'updated_count',
|
|
],
|
|
]);
|
|
|
|
$response->assertJson([
|
|
'success' => true,
|
|
'data' => [
|
|
'updated_count' => 3,
|
|
],
|
|
]);
|
|
|
|
// DB에서 실제로 변경되었는지 확인
|
|
foreach ($ids as $uuid) {
|
|
$this->assertDatabaseHas('users', [
|
|
'uuid' => $uuid,
|
|
'status' => 'active',
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 일괄 상태 변경 응답 메시지의 :count 플레이스홀더가 실제 처리 건수로 치환된다.
|
|
*
|
|
* 7.0.7 사전점검 브라우저 실측에서 응답 message 가
|
|
* ":count명의 사용자 상태가 변경되었습니다." 원문 그대로 내려오는 결함 발견 —
|
|
* 컨트롤러가 메시지 키만 넘기고 치환 파라미터를 생략한 회귀를 고정한다.
|
|
*/
|
|
public function test_bulk_status_message_substitutes_count_placeholder(): void
|
|
{
|
|
$users = User::factory()->count(2)->create(['status' => 'inactive']);
|
|
|
|
$response = $this->authRequest()->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => $users->pluck('uuid')->toArray(),
|
|
'status' => 'active',
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$message = (string) $response->json('message');
|
|
$this->assertStringNotContainsString(':count', $message, '응답 메시지에 미치환 플레이스홀더가 남아 있으면 안 됩니다');
|
|
$this->assertStringContainsString('2', $message, '응답 메시지에 처리 건수가 치환되어야 합니다');
|
|
}
|
|
|
|
/**
|
|
* 활성 사용자들을 비활성으로 일괄 변경할 수 있다.
|
|
*/
|
|
public function test_bulk_deactivate_users(): void
|
|
{
|
|
// 활성 사용자 3명 생성
|
|
$users = User::factory()->count(3)->create(['status' => 'active']);
|
|
$ids = $users->pluck('uuid')->toArray();
|
|
|
|
$response = $this->authRequest()->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => $ids,
|
|
'status' => 'inactive',
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$response->assertJson([
|
|
'success' => true,
|
|
'data' => [
|
|
'updated_count' => 3,
|
|
],
|
|
]);
|
|
|
|
// DB에서 실제로 변경되었는지 확인
|
|
foreach ($ids as $uuid) {
|
|
$this->assertDatabaseHas('users', [
|
|
'uuid' => $uuid,
|
|
'status' => 'inactive',
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 권한 없는 사용자의 요청은 403 오류를 반환한다.
|
|
*/
|
|
public function test_bulk_status_requires_permission(): void
|
|
{
|
|
// 권한 없는 사용자 생성
|
|
$normalUser = User::factory()->create();
|
|
$normalToken = $normalUser->createToken('test-token')->plainTextToken;
|
|
|
|
$users = User::factory()->count(2)->create(['status' => 'inactive']);
|
|
$ids = $users->pluck('uuid')->toArray();
|
|
|
|
$response = $this->withHeaders([
|
|
'Authorization' => 'Bearer '.$normalToken,
|
|
'Accept' => 'application/json',
|
|
])->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => $ids,
|
|
'status' => 'active',
|
|
]);
|
|
|
|
$response->assertStatus(403);
|
|
}
|
|
|
|
/**
|
|
* 잘못된 요청 데이터는 422 오류를 반환한다.
|
|
*/
|
|
public function test_bulk_status_validates_request(): void
|
|
{
|
|
// ids가 빈 배열인 경우
|
|
$response = $this->authRequest()->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => [],
|
|
'status' => 'active',
|
|
]);
|
|
$response->assertStatus(422);
|
|
|
|
// ids 필드 누락
|
|
$response = $this->authRequest()->patchJson('/api/admin/users/bulk-status', [
|
|
'status' => 'active',
|
|
]);
|
|
$response->assertStatus(422);
|
|
|
|
// status 필드 누락
|
|
$dummyUsers = User::factory()->count(3)->create();
|
|
$response = $this->authRequest()->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => $dummyUsers->pluck('uuid')->toArray(),
|
|
]);
|
|
$response->assertStatus(422);
|
|
|
|
// 잘못된 status 값
|
|
$response = $this->authRequest()->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => $dummyUsers->pluck('uuid')->toArray(),
|
|
'status' => 'invalid_status',
|
|
]);
|
|
$response->assertStatus(422);
|
|
|
|
// 존재하지 않는 사용자 UUID
|
|
$response = $this->authRequest()->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => ['00000000-0000-0000-0000-000000000000'],
|
|
'status' => 'active',
|
|
]);
|
|
$response->assertStatus(422);
|
|
}
|
|
|
|
/**
|
|
* 훅이 올바르게 실행되는지 확인한다.
|
|
*/
|
|
public function test_bulk_status_with_hooks(): void
|
|
{
|
|
$users = User::factory()->count(2)->create(['status' => 'inactive']);
|
|
$ids = $users->pluck('uuid')->toArray();
|
|
|
|
$beforeHookCalled = false;
|
|
$afterHookCalled = false;
|
|
|
|
// before_bulk_update 훅 등록
|
|
app(HookManager::class)->addAction('sirsoft-core.user.before_bulk_update', function ($hookIds, $hookStatus) use (&$beforeHookCalled, $ids) {
|
|
$beforeHookCalled = true;
|
|
$this->assertEquals($ids, $hookIds);
|
|
$this->assertEquals('active', $hookStatus);
|
|
});
|
|
|
|
// after_bulk_update 훅 등록
|
|
app(HookManager::class)->addAction('sirsoft-core.user.after_bulk_update', function ($hookIds, $hookStatus, $hookUpdatedCount) use (&$afterHookCalled, $ids) {
|
|
$afterHookCalled = true;
|
|
$this->assertEquals($ids, $hookIds);
|
|
$this->assertEquals('active', $hookStatus);
|
|
$this->assertEquals(2, $hookUpdatedCount);
|
|
});
|
|
|
|
$response = $this->authRequest()->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => $ids,
|
|
'status' => 'active',
|
|
]);
|
|
|
|
$response->assertOk();
|
|
$this->assertTrue($beforeHookCalled, 'before_bulk_update hook should be called');
|
|
$this->assertTrue($afterHookCalled, 'after_bulk_update hook should be called');
|
|
}
|
|
|
|
/**
|
|
* 인증되지 않은 사용자는 401 오류를 반환한다.
|
|
*/
|
|
public function test_unauthenticated_user_cannot_bulk_update_status(): void
|
|
{
|
|
$users = User::factory()->count(2)->create(['status' => 'inactive']);
|
|
$ids = $users->pluck('uuid')->toArray();
|
|
|
|
$response = $this->patchJson('/api/admin/users/bulk-status', [
|
|
'ids' => $ids,
|
|
'status' => 'active',
|
|
]);
|
|
|
|
$response->assertStatus(401);
|
|
}
|
|
}
|