$slug, 'name' => ['ko' => '블라인드 사유 노출 테스트 게시판', 'en' => 'Blind Reason Exposure Board'], 'is_active' => true, 'secret_mode' => 'disabled', 'blocked_keywords' => [], ]; } protected function setUp(): void { parent::setUp(); $slug = $this->board->slug; $this->adminWithManage = $this->createAdminUser([ "sirsoft-board.{$slug}.admin.manage", "sirsoft-board.{$slug}.admin.posts.read", ]); } private function postUrl(string $suffix = ''): string { $slug = $this->board->slug; return "/api/modules/sirsoft-board/admin/board/{$slug}/posts{$suffix}"; } private function userPostUrl(string $suffix = ''): string { $slug = $this->board->slug; return "/api/modules/sirsoft-board/boards/{$slug}/posts{$suffix}"; } private function userCommentUrl(int $postId, string $suffix = ''): string { $slug = $this->board->slug; return "/api/modules/sirsoft-board/boards/{$slug}/posts/{$postId}/comments{$suffix}"; } /** * 관리 권한자가 블라인드된 게시글 상세 조회 시 사유가 action_logs 에 포함된다. */ public function test_manager_sees_blind_reason_in_post_action_logs(): void { $postId = $this->createTestPost(['status' => 'published']); $this->actingAs($this->adminWithManage)->patchJson( $this->postUrl("/{$postId}/blind"), ['reason' => '운영 정책 위반으로 블라인드 처리'] )->assertStatus(200); $response = $this->actingAs($this->adminWithManage)->getJson($this->postUrl("/{$postId}")); $response->assertStatus(200); $logs = $response->json('data.action_logs'); $this->assertIsArray($logs, '관리자 응답에 action_logs 가 포함되어야 합니다.'); $blind = collect($logs)->firstWhere('action', 'blind'); $this->assertNotNull($blind, 'blind 액션 로그가 있어야 합니다.'); $this->assertSame('운영 정책 위반으로 블라인드 처리', $blind['reason']); // 민감 필드는 노출되지 않아야 한다. $this->assertArrayNotHasKey('admin_id', $blind, 'admin_id 는 노출되면 안 됩니다.'); $this->assertArrayNotHasKey('ip_address', $blind, 'ip_address 는 노출되면 안 됩니다.'); } /** * 일반 사용자(유저 화면)에게는 action_logs 가 노출되지 않는다 (사유 누출 방지). * * 블라인드 글은 유저가 상세 진입 자체가 막히므로, 블라인드→복원으로 published 상태이면서 * 처리 이력(action_logs)이 남은 게시글을 유저가 조회할 때 사유가 새지 않는지 검증한다. */ public function test_regular_user_does_not_see_post_action_logs(): void { $this->setGuestPermissions(['posts.read']); $postId = $this->createTestPost(['status' => 'published']); // action_logs 누적: Service 로 직접 블라인드→복원 (게스트 조회 전 인증 상태 잔존 방지) $service = app(PostService::class); $service->blindPost($this->board->slug, $postId, '민감 사유 — 누출 금지', 'admin'); $service->restorePost($this->board->slug, $postId, '복원', 'admin'); // 비로그인 사용자(게스트)로 유저 화면 상세 조회 $response = $this->getJson($this->userPostUrl("/{$postId}")); $response->assertStatus(200); $this->assertNull( $response->json('data.action_logs'), '유저 화면 응답에는 action_logs 가 노출되면 안 됩니다.' ); } /** * 관리 권한자가 블라인드 처리한 댓글 응답에 사유가 action_logs 에 포함된다. (#413-71-1) */ public function test_manager_sees_blind_reason_in_comment_action_logs(): void { $postId = $this->createTestPost(['status' => 'published']); $commentId = $this->createTestComment($postId); $response = $this->actingAs($this->adminWithManage)->patchJson( $this->postUrl("/{$postId}/comments/{$commentId}/blind"), ['reason' => '댓글 운영 정책 위반으로 블라인드'] ); $response->assertStatus(200); $logs = $response->json('data.action_logs'); $this->assertIsArray($logs, '관리자 댓글 응답에 action_logs 가 포함되어야 합니다.'); $blind = collect($logs)->firstWhere('action', 'blind'); $this->assertNotNull($blind, 'blind 액션 로그가 있어야 합니다.'); $this->assertSame('댓글 운영 정책 위반으로 블라인드', $blind['reason']); // 민감 필드는 노출되지 않아야 한다. $this->assertArrayNotHasKey('admin_id', $blind, 'admin_id 는 노출되면 안 됩니다.'); $this->assertArrayNotHasKey('ip_address', $blind, 'ip_address 는 노출되면 안 됩니다.'); } /** * 유저 화면 댓글 목록에는 action_logs 가 노출되지 않는다 (사유 누출 방지). (#413-71-1) */ public function test_regular_user_does_not_see_comment_action_logs(): void { $this->setGuestPermissions(['posts.read', 'comments.read']); $postId = $this->createTestPost(['status' => 'published']); $commentId = $this->createTestComment($postId); // action_logs 누적: Service 로 직접 블라인드→복원 (게스트 조회 전 인증 상태 잔존 방지) $service = app(CommentService::class); $service->blindComment($this->board->slug, $commentId, '민감 사유 — 누출 금지', 'admin'); $service->restoreComment($this->board->slug, $commentId, '복원', 'admin'); // 비로그인 사용자(게스트)로 유저 화면 댓글 목록 조회 $response = $this->getJson($this->userCommentUrl($postId)); $response->assertStatus(200); $comments = $response->json('data') ?? []; $this->assertNotEmpty($comments, '댓글 목록이 비어 있으면 안 됩니다.'); foreach ($comments as $comment) { $this->assertNull( $comment['action_logs'] ?? null, '유저 화면 댓글에는 action_logs 사유가 노출되면 안 됩니다.' ); } } }