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

297 lines
12 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Tests\Feature\User;
// 테스트 베이스 클래스 수동 require (autoload 전에 로드 필요)
require_once __DIR__.'/../../ModuleTestCase.php';
use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Modules\Sirsoft\Board\Tests\BoardTestCase;
/**
* 사용자 게시판 삭제 글/댓글 포함 토글 테스트
*
* manager 권한 보유 + del=1 파라미터가 있을 때만
* 삭제된 게시글/댓글이 목록에 노출되는지 검증합니다.
*
* @scenario viewer=manager
* @effects manager_sees_deleted_post_original_title_and_content, manager_sees_deleted_comment_original_content
*/
class PostDeletedToggleTest extends BoardTestCase
{
private User $regularUser;
private User $managerUser;
protected function getTestBoardSlug(): string
{
return 'deleted-toggle';
}
protected function getDefaultBoardAttributes(string $slug): array
{
return [
'slug' => $slug,
'name' => ['ko' => '삭제 토글 테스트 게시판', 'en' => 'Deleted Toggle Test Board'],
'is_active' => true,
'use_comment' => true,
'secret_mode' => 'disabled',
'blocked_keywords' => [],
];
}
protected function setUp(): void
{
parent::setUp();
$slug = $this->board->slug;
// 일반 사용자 (posts.read 권한만)
$this->regularUser = User::factory()->create();
$userRole = Role::where('identifier', 'user')->first();
if ($userRole) {
$readPerm = Permission::firstOrCreate(
['identifier' => "sirsoft-board.{$slug}.posts.read"],
['name' => ['ko' => '게시글 조회', 'en' => 'Read Posts'], 'type' => 'user']
);
$userRole->permissions()->syncWithoutDetaching([$readPerm->id]);
$this->regularUser->roles()->attach($userRole->id);
}
// manager 권한 사용자 (posts.read + manager)
$this->managerUser = User::factory()->create();
$managerRole = Role::firstOrCreate(
['identifier' => "{$slug}-manager"],
['name' => ['ko' => '게시판 매니저', 'en' => 'Board Manager']]
);
foreach (['posts.read', 'comments.read', 'manager'] as $key) {
$perm = Permission::firstOrCreate(
['identifier' => "sirsoft-board.{$slug}.{$key}"],
['name' => ['ko' => $key, 'en' => $key], 'type' => 'user']
);
$managerRole->permissions()->syncWithoutDetaching([$perm->id]);
}
$this->managerUser->roles()->attach($managerRole->id);
}
// ==========================================
// can_view_deleted abilities 키 테스트
// ==========================================
/**
* manager 권한 사용자는 abilities.can_view_deleted = true 반환
*/
public function test_manager_receives_can_view_deleted_true_in_abilities(): void
{
$response = $this->actingAs($this->managerUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts");
$response->assertStatus(200);
$this->assertTrue($response->json('data.abilities.can_view_deleted'));
}
/**
* 일반 사용자는 abilities.can_view_deleted = false 반환
*/
public function test_regular_user_receives_can_view_deleted_false_in_abilities(): void
{
$response = $this->actingAs($this->regularUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts");
$response->assertStatus(200);
$this->assertFalse($response->json('data.abilities.can_view_deleted'));
}
// ==========================================
// include_deleted 토글 테스트 (게시글 목록)
// ==========================================
/**
* manager + del=1 → 삭제된 게시글 목록에 포함
*/
public function test_manager_with_include_deleted_sees_deleted_posts(): void
{
// Given: 일반 게시글 1개 + 삭제된 게시글 1개
$normalPostId = $this->createTestPost(['title' => '일반 게시글']);
$deletedPostId = $this->createTestPost([
'title' => '삭제된 게시글',
'deleted_at' => now(),
]);
// When: manager가 del=1로 목록 조회
$response = $this->actingAs($this->managerUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts?del=1");
$response->assertStatus(200);
$postIds = collect($response->json('data.data'))->pluck('id')->toArray();
$this->assertContains($deletedPostId, $postIds, '삭제된 게시글이 목록에 포함되어야 합니다');
$this->assertContains($normalPostId, $postIds, '일반 게시글도 목록에 포함되어야 합니다');
}
/**
* manager라도 include_deleted 파라미터 없으면 삭제된 게시글 미포함
*/
public function test_manager_without_include_deleted_does_not_see_deleted_posts(): void
{
// Given: 삭제된 게시글 생성
$deletedPostId = $this->createTestPost([
'title' => '삭제된 게시글',
'deleted_at' => now(),
]);
// When: include_deleted 파라미터 없이 조회
$response = $this->actingAs($this->managerUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts");
$response->assertStatus(200);
$postIds = collect($response->json('data.data'))->pluck('id')->toArray();
$this->assertNotContains($deletedPostId, $postIds, '삭제된 게시글이 목록에 포함되지 않아야 합니다');
}
/**
* 일반 사용자는 del=1 파라미터를 넘겨도 삭제된 게시글 미포함
*/
public function test_regular_user_cannot_see_deleted_posts_even_with_include_deleted(): void
{
// Given: 삭제된 게시글 생성
$deletedPostId = $this->createTestPost([
'title' => '삭제된 게시글',
'deleted_at' => now(),
]);
// When: 일반 사용자가 del=1로 조회 시도
$response = $this->actingAs($this->regularUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts?del=1");
$response->assertStatus(200);
$postIds = collect($response->json('data.data'))->pluck('id')->toArray();
$this->assertNotContains($deletedPostId, $postIds, '일반 사용자에게는 삭제된 게시글이 노출되지 않아야 합니다');
}
// ==========================================
// include_deleted_comments 토글 테스트 (댓글)
// ==========================================
/**
* manager + del_cmt=1 → 삭제된 댓글 상세에 포함
*/
public function test_manager_with_include_deleted_comments_sees_deleted_comments(): void
{
// Given: 게시글 + 일반 댓글 + 삭제된 댓글
$postId = $this->createTestPost(['title' => '댓글 테스트 게시글']);
$normalCommentId = $this->createTestComment($postId, ['content' => '일반 댓글']);
$deletedCommentId = $this->createTestComment($postId, [
'content' => '삭제된 댓글',
'deleted_at' => now(),
]);
// When: manager가 del_cmt=1로 게시글 상세 조회
$response = $this->actingAs($this->managerUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}?del_cmt=1");
$response->assertStatus(200);
$commentIds = collect($response->json('data.comments'))->pluck('id')->toArray();
$this->assertContains($deletedCommentId, $commentIds, '삭제된 댓글이 포함되어야 합니다');
$this->assertContains($normalCommentId, $commentIds, '일반 댓글도 포함되어야 합니다');
}
/**
* manager라도 include_deleted_comments 없으면 삭제 댓글 미포함
*/
public function test_manager_without_include_deleted_comments_does_not_see_deleted_comments(): void
{
// Given
$postId = $this->createTestPost(['title' => '댓글 테스트 게시글']);
$deletedCommentId = $this->createTestComment($postId, [
'content' => '삭제된 댓글',
'deleted_at' => now(),
]);
// When: include_deleted_comments 파라미터 없이 조회
$response = $this->actingAs($this->managerUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}");
$response->assertStatus(200);
$commentIds = collect($response->json('data.comments'))->pluck('id')->toArray();
$this->assertNotContains($deletedCommentId, $commentIds, '삭제된 댓글이 포함되지 않아야 합니다');
}
/**
* 일반 사용자는 del_cmt=1을 넘겨도 삭제 댓글 미포함
*/
public function test_regular_user_cannot_see_deleted_comments_even_with_include_deleted_comments(): void
{
// Given
$postId = $this->createTestPost(['title' => '댓글 테스트 게시글']);
$deletedCommentId = $this->createTestComment($postId, [
'content' => '삭제된 댓글',
'deleted_at' => now(),
]);
// When: 일반 사용자가 del_cmt=1로 조회 시도
$response = $this->actingAs($this->regularUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}?del_cmt=1");
$response->assertStatus(200);
$commentIds = collect($response->json('data.comments'))->pluck('id')->toArray();
$this->assertNotContains($deletedCommentId, $commentIds, '일반 사용자에게는 삭제된 댓글이 노출되지 않아야 합니다');
}
// ==========================================
// 삭제 게시글/댓글 원본 노출 권한 (#413-50-2)
// manager 권한자는 삭제글 상세에서 제목/본문 원본을 볼 수 있어야 한다.
// 기존: getFilteredTitle/Content 가 admin.manage 만 검사 → manager 진입자에게도
// "삭제된 게시글" 문구로 필터링되는 권한 불일치.
// ==========================================
/**
* manager 권한자는 삭제된 게시글 상세에서 제목/본문 원본을 본다.
*/
public function test_manager_sees_deleted_post_original_title_and_content(): void
{
$deletedPostId = $this->createTestPost([
'title' => '삭제글 원본 제목 ORIG',
'content' => '삭제글 원본 본문 ORIG',
'status' => 'deleted',
'deleted_at' => now(),
]);
$response = $this->actingAs($this->managerUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$deletedPostId}");
$response->assertStatus(200);
$this->assertSame('삭제글 원본 제목 ORIG', $response->json('data.title'));
$this->assertSame('삭제글 원본 본문 ORIG', $response->json('data.content'));
}
/**
* manager 권한자는 삭제된 댓글의 원본 본문을 본다.
*/
public function test_manager_sees_deleted_comment_original_content(): void
{
$postId = $this->createTestPost(['title' => '댓글 원본 테스트 게시글']);
$deletedCommentId = $this->createTestComment($postId, [
'content' => '삭제 댓글 원본 ORIG',
'deleted_at' => now(),
]);
$response = $this->actingAs($this->managerUser, 'sanctum')
->getJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}?del_cmt=1");
$response->assertStatus(200);
$deletedComment = collect($response->json('data.comments'))
->firstWhere('id', $deletedCommentId);
$this->assertNotNull($deletedComment, '삭제 댓글이 응답에 포함되어야 합니다');
$this->assertSame('삭제 댓글 원본 ORIG', $deletedComment['content']);
}
}