$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 로 마킹되어야 합니다'); } }