Files
2026-07-01 10:30:32 +09:00

187 lines
6.9 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Tests\Feature;
// ModuleTestCase를 수동으로 require (autoload 전에 로드 필요)
require_once __DIR__.'/../../ModuleTestCase.php';
use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Modules\Sirsoft\Board\Enums\ReportStatus;
use Modules\Sirsoft\Board\Models\Board;
use Modules\Sirsoft\Board\Models\Report;
use Modules\Sirsoft\Board\Tests\ModuleTestCase;
/**
* 영구삭제(deleted) 신고건의 강제 상태변경 차단 회귀 테스트 (#413-74)
*
* 검수: 영구삭제된 신고건을 백엔드 API로 강제로 다른 상태로 변경 요청하면
* 422를 반환해야 하는데 200으로 떨어지는 결함. (실제 DB 상태는 불변)
*
* 근본 원인:
* ① UpdateStatusRequest 의 status 검증 클로저가 라우트 파라미터를 잘못 읽어(route('id'))
* 항상 null 을 조회 → canTransitionTo 검증이 통째로 무력화
* ② 단건 컨트롤러가 벌크 메서드(bulkUpdateStatus)를 호출 → deleted 항목을 조용히 스킵(200)
*
* deleted 는 ReportStatus 전환 규칙상 최종 상태(canTransitionTo 항상 false)이므로
* 강제 변경 시도는 422 + invalid_status_transition 으로 거부되어야 한다.
*/
class ReportDeletedStatusGuardTest extends ModuleTestCase
{
private User $admin;
private Board $board;
protected function setUp(): void
{
parent::setUp();
config(['telescope.enabled' => false]);
App::setLocale('ko');
DB::table('boards_report_logs')->delete();
DB::table('boards_reports')->delete();
DB::table('users')->where('is_super', false)->delete();
$this->admin = User::factory()->create();
$viewPermission = Permission::firstOrCreate(
['identifier' => 'sirsoft-board.reports.view'],
['name' => ['ko' => '신고 조회', 'en' => 'View Reports'], 'type' => 'admin']
);
$managePermission = Permission::firstOrCreate(
['identifier' => 'sirsoft-board.reports.manage'],
['name' => ['ko' => '신고 관리', 'en' => 'Manage Reports'], 'type' => 'admin']
);
$adminRole = Role::where('identifier', 'admin')->first();
$adminRole->permissions()->syncWithoutDetaching([$viewPermission->id, $managePermission->id]);
$this->admin->roles()->attach($adminRole->id);
$this->board = Board::updateOrCreate(
['slug' => 'report-deleted-guard-board'],
[
'name' => ['ko' => '신고 차단 테스트 게시판', 'en' => 'Report Guard Test Board'],
'slug' => 'report-deleted-guard-board',
'type' => 'list',
'per_page' => 20,
'per_page_mobile' => 10,
'order_by' => 'created_at',
'order_direction' => 'DESC',
'secret_mode' => 'disabled',
'use_comment' => true,
'use_reply' => false,
'use_file_upload' => false,
'use_report' => true,
'blocked_keywords' => [],
'notify_admin_on_post' => false,
'notify_author_on_comment' => false,
]
);
DB::table('boards_report_logs')->delete();
DB::table('boards_reports')->delete();
}
/**
* 영구삭제된 신고건을 다른 상태로 강제 변경 요청하면 422 로 거부되고 상태가 불변임을 검증합니다.
*/
public function test_force_status_change_on_deleted_report_is_rejected_with_422(): void
{
// Given: deleted(영구삭제) 상태의 신고
$report = Report::factory()->create([
'board_id' => $this->board->id,
'status' => ReportStatus::Deleted,
]);
// When: review 로 강제 변경 시도
$response = $this->actingAs($this->admin)
->patchJson("/api/modules/sirsoft-board/admin/reports/{$report->id}/status", [
'status' => 'review',
'process_note' => '강제 변경 시도',
]);
// Then: 422 거부 + DB 상태는 여전히 deleted (불변)
$response->assertStatus(422);
$this->assertDatabaseHas('boards_reports', [
'id' => $report->id,
'status' => 'deleted',
]);
}
/**
* 영구삭제된 신고건을 pending/rejected/suspended 등 어떤 상태로 강제 요청해도 모두 422 임을 검증합니다.
*/
public function test_force_status_change_on_deleted_report_is_rejected_for_all_targets(): void
{
foreach (['pending', 'rejected', 'suspended'] as $targetStatus) {
$report = Report::factory()->create([
'board_id' => $this->board->id,
'status' => ReportStatus::Deleted,
]);
$response = $this->actingAs($this->admin)
->patchJson("/api/modules/sirsoft-board/admin/reports/{$report->id}/status", [
'status' => $targetStatus,
]);
$response->assertStatus(422);
$this->assertDatabaseHas('boards_reports', [
'id' => $report->id,
'status' => 'deleted',
]);
}
}
/**
* 동일 상태로의 변경 시도(pending → pending)도 전환 규칙 위반으로 422 임을 검증합니다.
* (A 수정으로 복원되는 전환 검증의 부수 효과 — 동일 상태 전환 차단)
*/
public function test_same_status_transition_is_rejected_with_422(): void
{
$report = Report::factory()->create([
'board_id' => $this->board->id,
'status' => ReportStatus::Pending,
]);
$response = $this->actingAs($this->admin)
->patchJson("/api/modules/sirsoft-board/admin/reports/{$report->id}/status", [
'status' => 'pending',
]);
$response->assertStatus(422);
}
/**
* 정상 전환(pending → review)은 200 으로 처리되고 상태가 변경됨을 검증합니다.
* (A 수정이 정상 전환을 깨지 않음을 보장하는 회귀 가드)
*/
public function test_valid_status_transition_still_succeeds(): void
{
$report = Report::factory()->create([
'board_id' => $this->board->id,
'status' => ReportStatus::Pending,
]);
$response = $this->actingAs($this->admin)
->patchJson("/api/modules/sirsoft-board/admin/reports/{$report->id}/status", [
'status' => 'review',
'process_note' => '검토 시작',
]);
$response->assertStatus(200)
->assertJson(['success' => true]);
$this->assertDatabaseHas('boards_reports', [
'id' => $report->id,
'status' => 'review',
'processed_by' => $this->admin->id,
]);
}
}