Files
HeuJung 52ee8c855a chore(release): 공개 소스 본문 내부 단서(· 호칭) 정리
공개 release 발행 시 파일 본문 누출 검사(check-public-release-leakage)에
걸리던 내부 단서를 정리해 발행을 차단하지 않도록 함.

- 코드/테스트 주석·문서 본문의 "" 규정 인용을 "코어 규정/규칙" 등
 중립 표현으로 치환 (코어·이커머스·board·kginicis·gdpr·pay·템플릿 전 영역)
- 내부 역할 호칭 ""/""를 문맥에 맞는 중립 표현으로 일반화
- config/app.php 의 보호경로 목록(G7_UPDATE_PROTECTED_PATHS) 내 파일명은
 기능 코드이므로 삭제 대신 검사기 화이트리스트에 등록

주석/설명 문자열/문서 본문만 변경 — 로직·데이터·동작 무변경.
검증: 누출 재검사 error 0 / warn 0, 영향 테스트 TS 140/140·PHP 113/113 통과.
2026-07-02 17:58:33 +09:00

195 lines
8.4 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Tests\Feature\Admin;
require_once __DIR__.'/../../ModuleTestCase.php';
use Illuminate\Support\Facades\DB;
use Modules\Sirsoft\Board\Services\PostService;
use Modules\Sirsoft\Board\Tests\BoardTestCase;
/**
* 게시글 삭제 연쇄 처리 테스트 (#413-69-②)
*
* 방법1(연쇄 soft delete + cascade 플래그 기반 선별 복원) 검증:
* - 게시글 삭제 시 살아있던 댓글·첨부가 cascade soft delete + trigger_type='cascade' 마킹
* - 삭제 전 사용자가 따로 지운 댓글/첨부(trigger_type='user')는 영향 없음
* - 게시글 복원 시 cascade 항목만 restore, 사용자가 먼저 지운 것은 trashed 유지
* - 삭제 경로(관리자/사용자/신고) 무관하게 cascade 동작
*
* @group board
* @group admin
*/
class PostDeleteCascadeTest extends BoardTestCase
{
private PostService $postService;
protected function getTestBoardSlug(): string
{
return 'post-delete-cascade';
}
protected function getDefaultBoardAttributes(string $slug): array
{
return [
'slug' => $slug,
'name' => ['ko' => '삭제 연쇄 테스트 게시판', 'en' => 'Delete Cascade Board'],
'is_active' => true,
'secret_mode' => 'disabled',
'blocked_keywords' => [],
];
}
protected function setUp(): void
{
parent::setUp();
$this->postService = app(PostService::class);
}
/**
* 첨부파일을 직접 DB 에 생성합니다.
*
* @param int $postId 게시글 ID
* @param array $attributes 덮어쓸 속성
* @return int 생성된 첨부 ID
*/
private function createTestAttachment(int $postId, array $attributes = []): int
{
static $seq = 0;
$seq++;
$defaults = [
'board_id' => $this->board->id,
'post_id' => $postId,
'hash' => 'h'.str_pad((string) $seq, 11, '0', STR_PAD_LEFT),
'original_filename' => 'test.png',
'stored_filename' => 'stored-'.$seq.'.png',
'disk' => 'local',
'path' => 'attachments/test-'.$seq.'.png',
'mime_type' => 'image/png',
'size' => 1024,
'collection' => 'default',
'order' => 0,
'created_at' => now(),
'updated_at' => now(),
];
return DB::table('board_attachments')->insertGetId(array_merge($defaults, $attributes));
}
/**
* 게시글 삭제 시 살아있던 댓글·첨부가 cascade 로 soft delete 되고 trigger_type='cascade' 로 마킹된다.
*
* @scenario delete_trigger=admin
*
* @effects live_comment_cascade_soft_deleted,live_attachment_cascade_soft_deleted,cascade_trigger_type_marked,delete_and_restore_wrapped_in_transaction
*/
public function test_deleting_post_cascades_to_comments_and_attachments(): void
{
$postId = $this->createTestPost(['status' => 'published']);
$commentId = $this->createTestComment($postId);
$attachmentId = $this->createTestAttachment($postId);
$this->postService->deletePost($this->board->slug, $postId, 'admin');
$comment = DB::table('board_comments')->where('id', $commentId)->first();
$attachment = DB::table('board_attachments')->where('id', $attachmentId)->first();
$this->assertNotNull($comment->deleted_at, '댓글이 soft delete 되어야 합니다.');
$this->assertSame('cascade', $comment->trigger_type, '댓글이 cascade 로 마킹되어야 합니다.');
$this->assertNotNull($attachment->deleted_at, '첨부가 soft delete 되어야 합니다.');
$this->assertSame('cascade', $attachment->trigger_type, '첨부가 cascade 로 마킹되어야 합니다.');
}
/**
* 게시글 삭제 전 사용자가 따로 지운 댓글/첨부(trigger_type='user')는 cascade 의 영향을 받지 않는다.
*
* @effects user_pre_deleted_comment_untouched,user_pre_deleted_attachment_untouched
*/
public function test_pre_deleted_items_are_not_overwritten_by_cascade(): void
{
$postId = $this->createTestPost(['status' => 'published']);
// 사용자가 먼저 지운 댓글/첨부 (trigger_type='user', 이미 trashed)
$userDeletedCommentId = $this->createTestComment($postId, [
'trigger_type' => 'user',
'deleted_at' => now()->subHour(),
]);
$userDeletedAttachmentId = $this->createTestAttachment($postId, [
'trigger_type' => 'user',
'deleted_at' => now()->subHour(),
]);
$this->postService->deletePost($this->board->slug, $postId, 'admin');
$comment = DB::table('board_comments')->where('id', $userDeletedCommentId)->first();
$attachment = DB::table('board_attachments')->where('id', $userDeletedAttachmentId)->first();
$this->assertSame('user', $comment->trigger_type, '사용자가 먼저 지운 댓글의 trigger_type 은 user 로 유지되어야 합니다.');
$this->assertSame('user', $attachment->trigger_type, '사용자가 먼저 지운 첨부의 trigger_type 은 user 로 유지되어야 합니다.');
}
/**
* 게시글 복원 시 cascade 항목만 복원되고, 사용자가 먼저 지운 것은 trashed 로 유지된다 (우려 차단).
*
* @effects cascaded_comment_restored_on_post_restore,cascaded_attachment_restored_on_post_restore,user_pre_deleted_comment_stays_trashed,user_pre_deleted_attachment_stays_trashed
*/
public function test_restoring_post_only_restores_cascaded_items(): void
{
$postId = $this->createTestPost(['status' => 'published']);
$liveCommentId = $this->createTestComment($postId);
$liveAttachmentId = $this->createTestAttachment($postId);
$userDeletedCommentId = $this->createTestComment($postId, [
'trigger_type' => 'user',
'deleted_at' => now()->subHour(),
]);
$userDeletedAttachmentId = $this->createTestAttachment($postId, [
'trigger_type' => 'user',
'deleted_at' => now()->subHour(),
]);
$this->postService->deletePost($this->board->slug, $postId, 'admin');
$this->postService->restorePost($this->board->slug, $postId, '복원', 'admin');
// cascade 로 지워졌던 것은 복원
$liveComment = DB::table('board_comments')->where('id', $liveCommentId)->first();
$liveAttachment = DB::table('board_attachments')->where('id', $liveAttachmentId)->first();
$this->assertNull($liveComment->deleted_at, 'cascade 댓글은 복원되어야 합니다.');
$this->assertNull($liveAttachment->deleted_at, 'cascade 첨부는 복원되어야 합니다.');
// 사용자가 먼저 지운 것은 여전히 trashed
$userComment = DB::table('board_comments')->where('id', $userDeletedCommentId)->first();
$userAttachment = DB::table('board_attachments')->where('id', $userDeletedAttachmentId)->first();
$this->assertNotNull($userComment->deleted_at, '사용자가 먼저 지운 댓글은 복원되면 안 됩니다.');
$this->assertNotNull($userAttachment->deleted_at, '사용자가 먼저 지운 첨부는 복원되면 안 됩니다.');
}
/**
* 사용자 본인 삭제 경로(trigger_type='user')에서도 게시글의 댓글·첨부가 cascade 처리된다.
*
* @scenario delete_trigger=user
*
* @effects live_comment_cascade_soft_deleted,live_attachment_cascade_soft_deleted,cascade_trigger_type_marked
*/
public function test_cascade_works_for_user_delete_path(): void
{
$postId = $this->createTestPost(['status' => 'published']);
$commentId = $this->createTestComment($postId);
$attachmentId = $this->createTestAttachment($postId);
$this->postService->deletePost($this->board->slug, $postId, 'user');
$comment = DB::table('board_comments')->where('id', $commentId)->first();
$attachment = DB::table('board_attachments')->where('id', $attachmentId)->first();
$this->assertNotNull($comment->deleted_at, '사용자 삭제 경로에서도 댓글이 cascade soft delete 되어야 합니다.');
$this->assertSame('cascade', $comment->trigger_type);
$this->assertNotNull($attachment->deleted_at, '사용자 삭제 경로에서도 첨부가 cascade soft delete 되어야 합니다.');
$this->assertSame('cascade', $attachment->trigger_type);
}
}