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

819 lines
30 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Tests\Feature\User;
// 테스트 베이스 클래스 수동 require (autoload 전에 로드 필요)
require_once __DIR__.'/../../ModuleTestCase.php';
use App\Models\Role;
use App\Models\User;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Modules\Sirsoft\Board\Tests\BoardTestCase;
/**
* 게시글 생성/수정/삭제 API 테스트
*
* 게시판 설정에 따른 게시글 CRUD 동작 검증:
* - 비회원 권한 (role/permission 시스템)
* - 파일 업로드 허용/불허 (use_file_upload)
* - 비밀글 모드 (secret_mode: disabled, enabled, always)
* - 금지 키워드 검증 (blocked_keywords)
* - 수정/삭제 권한 (작성자, 비밀번호)
*/
class PostMutationApiTest extends BoardTestCase
{
private User $memberUser;
private User $otherUser;
/**
* 테스트 게시판 slug
*/
protected function getTestBoardSlug(): string
{
return 'mutation-test';
}
/**
* 기본 게시판 속성
*/
protected function getDefaultBoardAttributes(string $slug): array
{
return [
'slug' => $slug,
'name' => ['ko' => '뮤테이션 테스트 게시판', 'en' => 'Mutation Test Board'],
'is_active' => true,
'use_file_upload' => true,
'secret_mode' => 'enabled',
'blocked_keywords' => [],
];
}
protected function setUp(): void
{
parent::setUp();
// user 역할에 게시판 권한 부여
$this->grantUserRolePermissions();
// 테스트 사용자 생성 (parent::setUp()에서 users 테이블 초기화됨)
$this->memberUser = User::factory()->create(['email' => 'member@test.com']);
$this->otherUser = User::factory()->create(['email' => 'other@test.com']);
// member 사용자에 user 역할 부여
$userRole = Role::where('identifier', 'user')->first();
$this->memberUser->roles()->attach($userRole->id);
$this->otherUser->roles()->attach($userRole->id);
}
protected function tearDown(): void
{
parent::tearDown();
}
/**
* 테스트 게시글을 생성합니다.
*/
protected function createTestPost(array $attributes = []): int
{
$defaults = [
'board_id' => $this->board->id,
'title' => '테스트 게시글',
'content' => '테스트 내용입니다.',
'user_id' => null,
'author_name' => '테스트',
'password' => null,
'ip_address' => '127.0.0.1',
'is_notice' => false,
'is_secret' => false,
'status' => 'published',
'trigger_type' => 'admin',
'view_count' => 0,
'created_at' => now(),
'updated_at' => now(),
];
return DB::table('board_posts')->insertGetId(array_merge($defaults, $attributes));
}
// ==========================================
// 게시글 생성 테스트 (POST /boards/{slug}/posts)
// ==========================================
/**
* 회원이 게시글을 생성할 수 있다
*/
public function test_member_can_create_post(): void
{
// When: 회원이 게시글 생성 요청
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '회원 게시글',
'content' => '회원이 작성한 내용입니다.',
'author_name' => '회원작성자',
]);
// Then: 201 성공
$response->assertStatus(201)
->assertJsonPath('success', true)
->assertJsonPath('data.title', '회원 게시글');
// 데이터베이스 확인
$this->assertDatabaseHas('board_posts', [
'board_id' => $this->board->id,
'title' => '회원 게시글',
'user_id' => $this->memberUser->id,
]);
}
/**
* 비회원이 글쓰기 허용된 게시판에서 게시글을 생성할 수 있다
*/
public function test_guest_can_create_post_when_allowed(): void
{
// Given: 비회원 글쓰기 허용 (기본 설정)
// When: 비회원이 게시글 생성 요청
$response = $this->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '비회원 게시글',
'content' => '비회원이 작성한 내용입니다.',
'author_name' => '비회원',
'password' => 'guest1234',
]);
// Then: 201 성공
$response->assertStatus(201)
->assertJsonPath('success', true)
->assertJsonPath('data.title', '비회원 게시글');
// 비밀번호가 해시되어 저장되었는지 확인
$post = DB::table('board_posts')->where('board_id', $this->board->id)->first();
$this->assertTrue(Hash::check('guest1234', $post->password));
}
/**
* 비회원이 글쓰기 불허 게시판에서 게시글 생성 시 401 반환
*/
public function test_guest_cannot_create_post_when_not_allowed(): void
{
// Given: 비회원 글쓰기 불허 (posts.write 권한 제거)
$this->setGuestPermissions(['posts.read']);
// When: 비회원이 게시글 생성 요청
$response = $this->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '비회원 게시글',
'content' => '비회원이 작성한 내용입니다.',
'author_name' => '비회원',
'password' => 'guest1234',
]);
// Then: 401 에러 (PermissionMiddleware는 guest 권한 부족 시 401 반환)
$response->assertStatus(401)
->assertJsonPath('success', false);
}
/**
* 파일 업로드가 불허된 게시판에서 파일 첨부 시 403 반환
*/
public function test_file_upload_blocked_when_not_allowed(): void
{
// Given: 파일 업로드 불허
$this->updateBoardSettings(['use_file_upload' => false]);
// 테스트용 가짜 파일 생성
$file = UploadedFile::fake()->create('test.pdf', 100);
// When: 파일과 함께 게시글 생성 요청
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '파일 첨부 게시글',
'content' => '충분한 길이의 테스트 내용입니다.',
'author_name' => '작성자',
'files' => [$file],
]);
// Then: 403 에러
$response->assertStatus(403)
->assertJsonPath('success', false);
}
/**
* 비회원이 파일 업로드 권한 없이 파일 첨부 시 403 반환
*/
public function test_guest_file_upload_blocked_when_no_permission(): void
{
// Given: 비회원 파일 업로드 권한 없음 (attachments.upload 권한 제거)
$this->updateBoardSettings(['use_file_upload' => true]);
$this->setGuestPermissions(['posts.read', 'posts.write']); // attachments.upload 없음
// 테스트용 가짜 파일 생성
$file = UploadedFile::fake()->create('test.pdf', 100);
// When: 비회원이 파일과 함께 게시글 생성 요청
$response = $this->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '파일 첨부 게시글',
'content' => '충분한 길이의 테스트 내용입니다.',
'author_name' => '비회원',
'password' => 'guest1234',
'files' => [$file],
]);
// Then: 403 에러
$response->assertStatus(403)
->assertJsonPath('success', false);
}
/**
* 비회원이 파일 업로드 권한 있을 때 파일 첨부 가능
*/
public function test_guest_can_upload_file_with_permission(): void
{
// Given: 비회원 파일 업로드 권한 있음
// 기본 설정에 attachments.upload 포함되어 있음
// 테스트용 가짜 파일 생성
$file = UploadedFile::fake()->create('test.pdf', 100);
// When: 비회원이 파일과 함께 게시글 생성 요청
$response = $this->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '파일 첨부 게시글',
'content' => '충분한 길이의 테스트 내용입니다.',
'author_name' => '비회원',
'password' => 'guest1234',
'files' => [$file],
]);
// Then: 201 성공
$response->assertStatus(201)
->assertJsonPath('success', true);
}
/**
* secret_mode가 always일 때 is_secret이 자동으로 true 설정
*/
public function test_secret_mode_always_auto_sets_is_secret(): void
{
// Given: secret_mode = always
$this->updateBoardSettings(['secret_mode' => 'always']);
// When: is_secret을 명시하지 않고 게시글 생성
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '일반 게시글',
'content' => '충분한 길이의 테스트 내용입니다.',
'author_name' => '작성자',
// is_secret 명시하지 않음
]);
// Then: 성공하고 is_secret이 true로 설정됨
$response->assertStatus(201)
->assertJsonPath('success', true)
->assertJsonPath('data.is_secret', true);
}
/**
* secret_mode가 always일 때 is_secret=false로 요청해도 true로 강제 설정
*/
public function test_secret_mode_always_forces_is_secret_even_when_false(): void
{
// Given: secret_mode = always
$this->updateBoardSettings(['secret_mode' => 'always']);
// When: is_secret을 false로 명시하고 게시글 생성
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '일반 게시글',
'content' => '충분한 길이의 테스트 내용입니다.',
'author_name' => '작성자',
'is_secret' => false,
]);
// Then: 성공하고 is_secret이 true로 강제 설정됨
$response->assertStatus(201)
->assertJsonPath('success', true)
->assertJsonPath('data.is_secret', true);
}
/**
* secret_mode가 enabled일 때 사용자가 is_secret 선택 가능
*/
public function test_secret_mode_enabled_allows_user_choice(): void
{
// Given: secret_mode = enabled (기본 설정)
// When: is_secret=true로 게시글 생성
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '비밀 게시글',
'content' => '충분한 길이의 비밀 내용입니다.',
'author_name' => '작성자',
'is_secret' => true,
]);
// Then: 성공하고 is_secret이 true
$response->assertStatus(201)
->assertJsonPath('data.is_secret', true);
}
/**
* secret_mode가 disabled일 때 is_secret=true 요청 거부
*/
public function test_secret_mode_disabled_rejects_is_secret(): void
{
// Given: secret_mode = disabled
$this->updateBoardSettings(['secret_mode' => 'disabled']);
// When: is_secret=true로 게시글 생성 시도
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '게시글',
'content' => '충분한 길이의 테스트 내용입니다.',
'author_name' => '작성자',
'is_secret' => true,
]);
// Then: 비밀글 기능 비활성화로 403 반환
$response->assertStatus(403)
->assertJsonPath('success', false);
}
/**
* 금지 키워드가 제목에 포함된 경우 422 반환
*/
public function test_blocked_keyword_in_title_returns_422(): void
{
// Given: 금지 키워드 설정
$this->updateBoardSettings(['blocked_keywords' => ['광고', '스팸']]);
// When: 금지 키워드가 포함된 제목으로 게시글 생성
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '이것은 광고입니다',
'content' => '충분한 길이의 정상 내용입니다.',
'author_name' => '작성자',
]);
// Then: 422 에러
$response->assertStatus(422)
->assertJsonValidationErrors(['title']);
}
/**
* 금지 키워드가 내용에 포함된 경우 422 반환
*/
public function test_blocked_keyword_in_content_returns_422(): void
{
// Given: 금지 키워드 설정
$this->updateBoardSettings(['blocked_keywords' => ['광고', '스팸']]);
// When: 금지 키워드가 포함된 내용으로 게시글 생성
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '정상 제목',
'content' => '이것은 스팸 내용입니다.',
'author_name' => '작성자',
]);
// Then: 422 에러
$response->assertStatus(422)
->assertJsonValidationErrors(['content']);
}
/**
* 필수 필드 누락 시 422 반환
*/
public function test_missing_required_fields_returns_422(): void
{
// When: 필수 필드 없이 요청
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
// title, content, author_name 누락
]);
// Then: 422 에러 (인증된 사용자에게 author_name은 필수 아님)
$response->assertStatus(422)
->assertJsonValidationErrors(['title', 'content']);
}
/**
* 비활성화된 게시판에 게시글 생성 시 404 반환
*/
public function test_create_post_on_inactive_board_returns_404(): void
{
// Given: 비활성화된 게시판
$this->updateBoardSettings(['is_active' => false]);
// When: 게시글 생성 요청
$response = $this->actingAs($this->memberUser)
->postJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts", [
'title' => '비활성 게시판 테스트',
'content' => '충분한 길이의 테스트 내용입니다.',
]);
// Then: 404 에러
$response->assertStatus(404);
}
// ==========================================
// 게시글 수정 테스트 (PUT /boards/{slug}/posts/{id})
// ==========================================
/**
* 작성자 본인이 회원 게시글을 수정할 수 있다
*/
public function test_author_can_update_own_member_post(): void
{
// Given: 회원 게시글 생성
$postId = $this->createTestPost([
'title' => '원본 제목',
'content' => '원본 내용',
'user_id' => $this->memberUser->id,
'author_name' => $this->memberUser->name,
]);
// When: 작성자가 수정 요청
$response = $this->actingAs($this->memberUser)
->putJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}", [
'title' => '수정된 제목',
'content' => '충분한 길이의 수정된 내용입니다.',
]);
// Then: 성공
$response->assertStatus(200)
->assertJsonPath('success', true)
->assertJsonPath('data.title', '수정된 제목');
}
/**
* 다른 사용자가 회원 게시글 수정 시 403 반환
*/
public function test_other_user_cannot_update_member_post(): void
{
// Given: 회원 게시글 생성
$postId = $this->createTestPost([
'user_id' => $this->memberUser->id,
'author_name' => $this->memberUser->name,
]);
// When: 다른 사용자가 수정 요청
$response = $this->actingAs($this->otherUser)
->putJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}", [
'title' => '수정된 제목',
]);
// Then: 403 에러
$response->assertStatus(403)
->assertJsonPath('success', false);
}
/**
* 게시판 관리(manager) 권한자는 타인 회원 게시글을 수정할 수 있다
*
* 회귀: 사용자 페이지 manager 권한자가 admin.manage 없이도 수정 200 (이슈 #413-20)
*/
public function test_manager_can_update_other_member_post(): void
{
// Given: manager 권한을 가진 사용자 (admin.manage 미보유)
$this->grantUserRolePermissions(['manager']);
$manager = User::factory()->create(['email' => 'manager@test.com']);
$manager->roles()->attach(Role::where('identifier', 'user')->first()->id);
$postId = $this->createTestPost([
'user_id' => $this->memberUser->id,
'author_name' => $this->memberUser->name,
]);
// When: manager 권한자가 타인 글 수정 요청
$response = $this->actingAs($manager)
->putJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}", [
'title' => '관리자가 수정한 제목',
'content' => '게시판 관리 권한으로 수정한 내용입니다.',
]);
// Then: 200 성공 (현재는 admin.manage 요구로 403 → 수정 후 200)
$response->assertStatus(200)
->assertJsonPath('success', true)
->assertJsonPath('data.title', '관리자가 수정한 제목');
}
/**
* 게시판 관리(manager) 권한자는 타인 회원 게시글을 삭제할 수 있다
*
* 회귀: 사용자 페이지 manager 권한자가 admin.manage 없이도 삭제 200 (이슈 #413-20)
*/
public function test_manager_can_delete_other_member_post(): void
{
// Given: manager 권한을 가진 사용자 (admin.manage 미보유)
$this->grantUserRolePermissions(['manager']);
$manager = User::factory()->create(['email' => 'manager-del@test.com']);
$manager->roles()->attach(Role::where('identifier', 'user')->first()->id);
$postId = $this->createTestPost([
'user_id' => $this->memberUser->id,
'author_name' => $this->memberUser->name,
]);
// When: manager 권한자가 타인 글 삭제 요청
$response = $this->actingAs($manager)
->deleteJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}");
// Then: 200 성공
$response->assertStatus(200)
->assertJsonPath('success', true);
}
/**
* 권한 없는 일반 사용자는 여전히 타인 게시글을 수정할 수 없다 (회귀 가드)
*/
public function test_non_manager_still_cannot_update_other_member_post(): void
{
// Given: manager 권한 없는 일반 사용자
$postId = $this->createTestPost([
'user_id' => $this->memberUser->id,
'author_name' => $this->memberUser->name,
]);
// When: otherUser(권한 없음)가 수정 요청
$response = $this->actingAs($this->otherUser)
->putJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}", [
'title' => '수정 시도',
]);
// Then: 403 유지
$response->assertStatus(403)
->assertJsonPath('success', false);
}
/**
* 비회원이 올바른 비밀번호로 비회원 게시글을 수정할 수 있다
*/
public function test_guest_can_update_own_post_with_correct_password(): void
{
// Given: 비회원 게시글 생성
$password = 'guest1234';
$postId = $this->createTestPost([
'title' => '비회원 게시글',
'user_id' => null,
'author_name' => '비회원',
'password' => Hash::make($password),
]);
// When: 올바른 비밀번호로 수정 요청
$response = $this->putJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}", [
'title' => '수정된 비회원 게시글',
'password' => $password,
]);
// Then: 성공
$response->assertStatus(200)
->assertJsonPath('success', true)
->assertJsonPath('data.title', '수정된 비회원 게시글');
}
/**
* 비회원이 잘못된 비밀번호로 수정 시 403 반환
*/
public function test_guest_cannot_update_post_with_wrong_password(): void
{
// Given: 비회원 게시글 생성
$postId = $this->createTestPost([
'user_id' => null,
'password' => Hash::make('correct_password'),
]);
// When: 잘못된 비밀번호로 수정 요청
$response = $this->putJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}", [
'title' => '수정된 제목',
'password' => 'wrong_password',
]);
// Then: 403 에러
$response->assertStatus(403)
->assertJsonPath('success', false);
}
/**
* 존재하지 않는 게시글 수정 시 404 반환
*/
public function test_update_non_existent_post_returns_404(): void
{
// When: 존재하지 않는 게시글 수정 요청
$response = $this->actingAs($this->memberUser)
->putJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/99999", [
'title' => '수정된 제목',
]);
// Then: 404 에러
$response->assertStatus(404);
}
/**
* 수정 시 금지 키워드가 포함되면 422 반환
*/
public function test_update_with_blocked_keyword_returns_422(): void
{
// Given: 금지 키워드 설정 및 게시글 생성
$this->updateBoardSettings(['blocked_keywords' => ['광고']]);
$postId = $this->createTestPost([
'user_id' => $this->memberUser->id,
'author_name' => $this->memberUser->name,
]);
// When: 금지 키워드가 포함된 내용으로 수정
$response = $this->actingAs($this->memberUser)
->putJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}", [
'title' => '광고 제목',
]);
// Then: 422 에러
$response->assertStatus(422)
->assertJsonValidationErrors(['title']);
}
// ==========================================
// 게시글 삭제 테스트 (DELETE /boards/{slug}/posts/{id})
// ==========================================
/**
* 작성자 본인이 회원 게시글을 삭제할 수 있다
*/
public function test_author_can_delete_own_member_post(): void
{
// Given: 회원 게시글 생성
$postId = $this->createTestPost([
'user_id' => $this->memberUser->id,
'author_name' => $this->memberUser->name,
]);
// When: 작성자가 삭제 요청
$response = $this->actingAs($this->memberUser)
->deleteJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}");
// Then: 성공
$response->assertStatus(200)
->assertJsonPath('success', true);
// 소프트 삭제 확인
$post = DB::table('board_posts')->where('id', $postId)->first();
$this->assertNotNull($post->deleted_at);
}
/**
* 다른 사용자가 회원 게시글 삭제 시 403 반환
*/
public function test_other_user_cannot_delete_member_post(): void
{
// Given: 회원 게시글 생성
$postId = $this->createTestPost([
'user_id' => $this->memberUser->id,
'author_name' => $this->memberUser->name,
]);
// When: 다른 사용자가 삭제 요청
$response = $this->actingAs($this->otherUser)
->deleteJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}");
// Then: 403 에러
$response->assertStatus(403)
->assertJsonPath('success', false);
}
/**
* 비회원이 올바른 비밀번호로 비회원 게시글을 삭제할 수 있다
*/
public function test_guest_can_delete_own_post_with_correct_password(): void
{
// Given: 비회원 게시글 생성
$password = 'guest1234';
$postId = $this->createTestPost([
'user_id' => null,
'author_name' => '비회원',
'password' => Hash::make($password),
]);
// When: 올바른 비밀번호로 삭제 요청
$response = $this->deleteJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}", [
'password' => $password,
]);
// Then: 성공
$response->assertStatus(200)
->assertJsonPath('success', true);
}
/**
* 비회원이 잘못된 비밀번호로 삭제 시 403 반환
*/
public function test_guest_cannot_delete_post_with_wrong_password(): void
{
// Given: 비회원 게시글 생성
$postId = $this->createTestPost([
'user_id' => null,
'password' => Hash::make('correct_password'),
]);
// When: 잘못된 비밀번호로 삭제 요청
$response = $this->deleteJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}", [
'password' => 'wrong_password',
]);
// Then: 403 에러
$response->assertStatus(403)
->assertJsonPath('success', false);
}
/**
* 존재하지 않는 게시글 삭제 시 404 반환
*/
public function test_delete_non_existent_post_returns_404(): void
{
// When: 존재하지 않는 게시글 삭제 요청
$response = $this->actingAs($this->memberUser)
->deleteJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/99999");
// Then: 404 에러
$response->assertStatus(404);
}
/**
* 비로그인 사용자가 비밀번호 없이 삭제 시 403 반환
*/
public function test_guest_without_password_cannot_delete_post(): void
{
// Given: 비회원 게시글 생성
$postId = $this->createTestPost([
'user_id' => null,
'password' => Hash::make('secret'),
]);
// When: 비밀번호 없이 삭제 요청
$response = $this->deleteJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}");
// Then: 403 에러
$response->assertStatus(403)
->assertJsonPath('success', false);
}
/**
* 게시글 소프트삭제 시 살아있던 댓글이 cascade 로 함께 soft delete 된다 (#413-69-②)
*
* 그룹2 에서 동작이 변경됨: 게시글 삭제 시 하위 댓글을 보존하지 않고
* trigger_type='cascade' 로 연쇄 soft delete 한다 (복원 시 cascade 분만 선별 부활).
*/
public function test_soft_delete_post_cascades_to_comments(): void
{
// Given: 게시글 + 댓글 생성
$postId = $this->createTestPost(['user_id' => $this->memberUser->id]);
$commentId = $this->createTestComment($postId);
// When: 게시글 삭제
$this->actingAs($this->memberUser)
->deleteJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}")
->assertStatus(200);
// Then: 댓글은 cascade 로 soft delete + trigger_type='cascade' 마킹
$comment = DB::table('board_comments')->where('id', $commentId)->first();
$this->assertNotNull($comment, '댓글 레코드는 DB에 존재해야 합니다(soft delete)');
$this->assertNotNull($comment->deleted_at, '게시글 삭제 시 댓글이 cascade soft delete 되어야 합니다');
$this->assertSame('cascade', $comment->trigger_type, '댓글이 cascade 로 마킹되어야 합니다');
}
/**
* 게시글 소프트삭제 시 살아있던 첨부가 cascade 로 함께 soft delete 된다 (#413-69-②)
*
* 그룹2 에서 동작이 변경됨: 게시글 삭제 시 첨부 레코드를 보존하지 않고
* trigger_type='cascade' 로 연쇄 soft delete 한다 (복원 시 cascade 분만 선별 부활).
*/
public function test_soft_delete_post_cascades_to_attachments(): void
{
// Given: 게시글 + 첨부파일 레코드 직접 삽입
$postId = $this->createTestPost(['user_id' => $this->memberUser->id]);
$attachmentId = DB::table('board_attachments')->insertGetId([
'board_id' => $this->board->id,
'post_id' => $postId,
'hash' => substr(md5(uniqid()), 0, 12),
'original_filename' => 'test.txt',
'stored_filename' => 'test_stored.txt',
'mime_type' => 'text/plain',
'size' => 100,
'disk' => 'local',
'path' => 'board/test_stored.txt',
'created_at' => now(),
'updated_at' => now(),
]);
// When: 게시글 삭제
$this->actingAs($this->memberUser)
->deleteJson("/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}")
->assertStatus(200);
// Then: 첨부 레코드는 cascade 로 soft delete + trigger_type='cascade' 마킹
$attachment = DB::table('board_attachments')->where('id', $attachmentId)->first();
$this->assertNotNull($attachment, '첨부 레코드는 DB에 존재해야 합니다(soft delete)');
$this->assertNotNull($attachment->deleted_at, '게시글 삭제 시 첨부가 cascade soft delete 되어야 합니다');
$this->assertSame('cascade', $attachment->trigger_type, '첨부가 cascade 로 마킹되어야 합니다');
}
}