diff --git a/src/main/java/konkuk/thip/book/adapter/out/persistence/repository/SavedBookJpaRepository.java b/src/main/java/konkuk/thip/book/adapter/out/persistence/repository/SavedBookJpaRepository.java index 19202fb07..234b9bfd4 100644 --- a/src/main/java/konkuk/thip/book/adapter/out/persistence/repository/SavedBookJpaRepository.java +++ b/src/main/java/konkuk/thip/book/adapter/out/persistence/repository/SavedBookJpaRepository.java @@ -10,7 +10,7 @@ public interface SavedBookJpaRepository extends JpaRepository, CommentQueryRepository { Optional findByCommentIdAndStatus(Long commentId, StatusType status); - @Modifying + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("UPDATE CommentJpaEntity c SET c.status = 'INACTIVE' WHERE c.postJpaEntity.postId = :postId") void softDeleteAllByPostId(@Param("postId") Long postId); diff --git a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java index 94abf46bc..8e4d9bb35 100644 --- a/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java +++ b/src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java @@ -15,7 +15,7 @@ public interface CommentLikeJpaRepository extends JpaRepository findAllByUserId(@Param("userId") Long userId); - @Modifying + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("DELETE FROM CommentLikeJpaEntity cl WHERE cl.userJpaEntity.userId = :userId AND cl.commentJpaEntity.commentId = :commentId") void deleteByUserIdAndCommentId(@Param("userId") Long userId, @Param("commentId") Long commentId); @@ -24,14 +24,14 @@ public interface CommentLikeJpaRepository extends JpaRepository findCommentIdsLikedByUser(@Param("commentIds") Set commentIds, @Param("userId") Long userId); - @Modifying + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(""" DELETE FROM CommentLikeJpaEntity cl WHERE cl.commentJpaEntity.commentId IN ( diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index 927b1928c..269c9ea22 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -120,6 +120,7 @@ public enum ErrorCode implements ResponseCode { RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, 130000, "존재하지 않는 RECORD 입니다."), RECORD_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 130001, "총평이 될 수 없는 RECORD 입니다."), INVALID_RECORD_PAGE_RANGE(HttpStatus.BAD_REQUEST, 130002, "RECORD의 page 값이 유효하지 않습니다."), + RECORD_ACCESS_FORBIDDEN(HttpStatus.FORBIDDEN, 130003, "기록 접근 권한이 없습니다."), /** * 140000 : roomParticipant error diff --git a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java index 71e357d42..c0cb92c2b 100644 --- a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java @@ -131,6 +131,11 @@ public enum SwaggerResponseDescription { BOOK_NOT_FOUND, ROOM_ACCESS_FORBIDDEN ))), + RECORD_DELETE(new LinkedHashSet<>(Set.of( + ROOM_ACCESS_FORBIDDEN, + RECORD_NOT_FOUND, + RECORD_ACCESS_FORBIDDEN + ))), // Vote VOTE_CREATE(new LinkedHashSet<>(Set.of( diff --git a/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/Content/ContentJpaRepository.java b/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/Content/ContentJpaRepository.java index 6c98eba8f..ac9e08eee 100644 --- a/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/Content/ContentJpaRepository.java +++ b/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/Content/ContentJpaRepository.java @@ -8,7 +8,7 @@ public interface ContentJpaRepository extends JpaRepository{ - @Modifying + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("DELETE FROM ContentJpaEntity c WHERE c.postJpaEntity.postId = :feedId") void deleteAllByFeedId(@Param("feedId") Long feedId); } diff --git a/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedTag/FeedTagJpaRepository.java b/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedTag/FeedTagJpaRepository.java index 9844d43ad..84f860857 100644 --- a/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedTag/FeedTagJpaRepository.java +++ b/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedTag/FeedTagJpaRepository.java @@ -18,7 +18,7 @@ public interface FeedTagJpaRepository extends JpaRepository findFeedIdAndTagsByFeedIds(@Param("feedIds") List feedIds); - @Modifying + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("DELETE FROM FeedTagJpaEntity ft WHERE ft.feedJpaEntity.postId = :feedId") void deleteAllByFeedId(@Param("feedId") Long feedId); } diff --git a/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java b/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java index c35735d51..9e4824066 100644 --- a/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java +++ b/src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java @@ -10,7 +10,8 @@ import java.util.Set; public interface SavedFeedJpaRepository extends JpaRepository { - @Modifying + + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("DELETE FROM SavedFeedJpaEntity sf WHERE sf.userJpaEntity.userId = :userId AND sf.feedJpaEntity.postId = :feedId") void deleteByUserIdAndFeedId(@Param("userId") Long userId, @Param("feedId") Long feedId); @@ -20,7 +21,7 @@ public interface SavedFeedJpaRepository extends JpaRepository findSavedFeedIdsByUserIdAndFeedIds(@Param("userId") Long userId, @Param("feedIds") Set feedIds); - @Modifying + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("DELETE FROM SavedFeedJpaEntity sf WHERE sf.feedJpaEntity.postId = :feedId") int deleteAllByFeedId(@Param("feedId") Long feedId); } diff --git a/src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java b/src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java index 1fb28d0bb..ef0e94f6f 100644 --- a/src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java +++ b/src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java @@ -29,8 +29,11 @@ public void deleteFeed(Long feedId, Long userId) { // TODO S3 이미지 삭제 이벤트 기반 처리 or 배치 삭제 // 3. 피드 삭제 + // 3-1. 피드 게시물 댓글 삭제 commentCommandPort.softDeleteAllByPostId(feedId); + // 3-2. 피드 게시물 좋아요 삭제 postLikeCommandPort.deleteAllByPostId(feedId); + // 3-3. 피드 삭제 및 관련 엔티티(피드_태그, 콘텐츠, 피드 저장) 삭제 feedCommandPort.delete(feed); } } diff --git a/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java b/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java index c5a88825d..8b291e553 100644 --- a/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java +++ b/src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java @@ -21,11 +21,11 @@ Set findPostIdsLikedByUser(@Param("postIds") Set postIds, "WHERE pl.userJpaEntity.userId = :userId AND pl.postJpaEntity.postId = :postId") boolean existsByUserIdAndPostId(@Param("userId") Long userId, @Param("postId") Long postId); - @Modifying + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("DELETE FROM PostLikeJpaEntity pl WHERE pl.userJpaEntity.userId = :userId AND pl.postJpaEntity.postId = :postId") void deleteByUserIdAndPostId(@Param("userId") Long userId, @Param("postId") Long postId); - @Modifying + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("DELETE FROM PostLikeJpaEntity pl WHERE pl.postJpaEntity.postId = :postId") void deleteAllByPostId(@Param("postId") Long postId); } diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java index 71a707798..654b18972 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java @@ -9,7 +9,7 @@ @Repository public interface RecentSearchJpaRepository extends JpaRepository, RecentSearchQueryRepository { - @Modifying + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("UPDATE RecentSearchJpaEntity r SET r.modifiedAt = CURRENT_TIMESTAMP WHERE r.recentSearchId = :recentSearchId") void updateModifiedAt(@Param("recentSearchId") Long recentSearchId); } diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java b/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java index dfca9e812..e5778f96a 100644 --- a/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java +++ b/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java @@ -9,12 +9,12 @@ import konkuk.thip.common.swagger.annotation.ExceptionDescription; import konkuk.thip.record.adapter.in.web.request.RecordCreateRequest; import konkuk.thip.record.adapter.in.web.response.RecordCreateResponse; +import konkuk.thip.record.adapter.in.web.response.RecordDeleteResponse; import konkuk.thip.record.application.port.in.RecordCreateUseCase; +import konkuk.thip.record.application.port.in.RecordDeleteUseCase; +import konkuk.thip.record.application.port.in.dto.RecordDeleteCommand; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import static konkuk.thip.common.swagger.SwaggerResponseDescription.*; @@ -23,6 +23,7 @@ @RequiredArgsConstructor public class RecordCommandController { private final RecordCreateUseCase recordCreateUseCase; + private final RecordDeleteUseCase recordDeleteUseCase; @Operation( summary = "기록 생성", @@ -41,4 +42,17 @@ public BaseResponse createRecord( )); } + @Operation( + summary = "기록 삭제", + description = "사용자가 기록을 삭제합니다." + ) + @ExceptionDescription(RECORD_DELETE) + @DeleteMapping("/rooms/{roomId}/record/{recordId}") + public BaseResponse deleteRecord( + @Parameter(description = "삭제하려는 기록 ID", example = "1") @PathVariable("recordId") final Long recordId, + @Parameter(description = "삭제하려는 기록이 작성된 모임 ID", example = "1") @PathVariable("roomId") final Long roomId, + @Parameter(hidden = true) @UserId final Long userId) { + return BaseResponse.ok(RecordDeleteResponse.of(recordDeleteUseCase.deleteRecord(new RecordDeleteCommand(roomId, recordId, userId)))); + } + } diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDeleteResponse.java b/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDeleteResponse.java new file mode 100644 index 000000000..b6bc76ca1 --- /dev/null +++ b/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordDeleteResponse.java @@ -0,0 +1,7 @@ +package konkuk.thip.record.adapter.in.web.response; + +public record RecordDeleteResponse(Long roomId) { + public static RecordDeleteResponse of(Long roomId) { + return new RecordDeleteResponse(roomId); + } +} diff --git a/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java b/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java index 33f3998c3..594abe8a0 100644 --- a/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java +++ b/src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java @@ -49,5 +49,10 @@ public void updateLikeCount(int likeCount) { this.likeCount = likeCount; } + @VisibleForTesting + public void updateCommentCount(int commentCount) { + this.commentCount = commentCount; + } + } diff --git a/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java index d1170074c..de74af625 100644 --- a/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java @@ -15,6 +15,7 @@ import java.util.Optional; +import static konkuk.thip.common.entity.StatusType.ACTIVE; import static konkuk.thip.common.exception.code.ErrorCode.*; @Repository @@ -43,10 +44,20 @@ public Long saveRecord(Record record) { @Override public Optional findById(Long id) { - return recordJpaRepository.findById(id) + return recordJpaRepository.findByPostIdAndStatus(id, ACTIVE) .map(recordMapper::toDomainEntity); } + @Override + public void delete(Record record) { + RecordJpaEntity recordJpaEntity = recordJpaRepository.findById(record.getId()).orElseThrow( + () -> new EntityNotFoundException(RECORD_NOT_FOUND) + ); + + recordJpaEntity.softDelete(); + recordJpaRepository.save(recordJpaEntity); + } + @Override public void update(Record record) { RecordJpaEntity recordJpaEntity = recordJpaRepository.findById(record.getId()).orElseThrow( diff --git a/src/main/java/konkuk/thip/record/adapter/out/persistence/repository/RecordJpaRepository.java b/src/main/java/konkuk/thip/record/adapter/out/persistence/repository/RecordJpaRepository.java index cb67058dc..a5058f7db 100644 --- a/src/main/java/konkuk/thip/record/adapter/out/persistence/repository/RecordJpaRepository.java +++ b/src/main/java/konkuk/thip/record/adapter/out/persistence/repository/RecordJpaRepository.java @@ -1,8 +1,11 @@ package konkuk.thip.record.adapter.out.persistence.repository; +import konkuk.thip.common.entity.StatusType; import konkuk.thip.record.adapter.out.jpa.RecordJpaEntity; import org.springframework.data.jpa.repository.JpaRepository; -public interface RecordJpaRepository extends JpaRepository, RecordQueryRepository { +import java.util.Optional; +public interface RecordJpaRepository extends JpaRepository, RecordQueryRepository { + Optional findByPostIdAndStatus(Long postId, StatusType status); } diff --git a/src/main/java/konkuk/thip/record/application/port/in/RecordDeleteUseCase.java b/src/main/java/konkuk/thip/record/application/port/in/RecordDeleteUseCase.java new file mode 100644 index 000000000..208454c84 --- /dev/null +++ b/src/main/java/konkuk/thip/record/application/port/in/RecordDeleteUseCase.java @@ -0,0 +1,7 @@ +package konkuk.thip.record.application.port.in; + +import konkuk.thip.record.application.port.in.dto.RecordDeleteCommand; + +public interface RecordDeleteUseCase { + Long deleteRecord(RecordDeleteCommand command); +} diff --git a/src/main/java/konkuk/thip/record/application/port/in/dto/DummyQuery.java b/src/main/java/konkuk/thip/record/application/port/in/dto/DummyQuery.java deleted file mode 100644 index 7d484a9f6..000000000 --- a/src/main/java/konkuk/thip/record/application/port/in/dto/DummyQuery.java +++ /dev/null @@ -1,9 +0,0 @@ -package konkuk.thip.record.application.port.in.dto; - -import lombok.Builder; -import lombok.Getter; - -@Builder -@Getter -public class DummyQuery { -} diff --git a/src/main/java/konkuk/thip/record/application/port/in/dto/DummyResult.java b/src/main/java/konkuk/thip/record/application/port/in/dto/DummyResult.java deleted file mode 100644 index 163e3648b..000000000 --- a/src/main/java/konkuk/thip/record/application/port/in/dto/DummyResult.java +++ /dev/null @@ -1,10 +0,0 @@ -package konkuk.thip.record.application.port.in.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class DummyResult { - -} diff --git a/src/main/java/konkuk/thip/record/application/port/in/dto/RecordDeleteCommand.java b/src/main/java/konkuk/thip/record/application/port/in/dto/RecordDeleteCommand.java new file mode 100644 index 000000000..e0f9218af --- /dev/null +++ b/src/main/java/konkuk/thip/record/application/port/in/dto/RecordDeleteCommand.java @@ -0,0 +1,10 @@ +package konkuk.thip.record.application.port.in.dto; + +public record RecordDeleteCommand( + Long roomId, + + Long recordId, + + Long userId +) { +} diff --git a/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java b/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java index a662daf86..5c23224b2 100644 --- a/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java +++ b/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java @@ -20,4 +20,6 @@ default Record getByIdOrThrow(Long id) { return findById(id) .orElseThrow(() -> new EntityNotFoundException(RECORD_NOT_FOUND)); } + + void delete(Record record); } diff --git a/src/main/java/konkuk/thip/record/application/service/RecordDeleteService.java b/src/main/java/konkuk/thip/record/application/service/RecordDeleteService.java new file mode 100644 index 000000000..aacd50f87 --- /dev/null +++ b/src/main/java/konkuk/thip/record/application/service/RecordDeleteService.java @@ -0,0 +1,46 @@ +package konkuk.thip.record.application.service; + +import jakarta.transaction.Transactional; +import konkuk.thip.comment.application.port.out.CommentCommandPort; +import konkuk.thip.post.application.port.out.PostLikeCommandPort; +import konkuk.thip.record.application.port.in.RecordDeleteUseCase; +import konkuk.thip.record.application.port.in.dto.RecordDeleteCommand; +import konkuk.thip.record.application.port.out.RecordCommandPort; +import konkuk.thip.record.domain.Record; +import konkuk.thip.room.application.service.validator.RoomParticipantValidator; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RecordDeleteService implements RecordDeleteUseCase { + + private final RecordCommandPort recordCommandPort; + private final CommentCommandPort commentCommandPort; + private final PostLikeCommandPort postLikeCommandPort; + + private final RoomParticipantValidator roomParticipantValidator; + + @Override + @Transactional + public Long deleteRecord(RecordDeleteCommand command) { + + // 1. 방 참여자 검증 + roomParticipantValidator.validateUserIsRoomMember(command.roomId(), command.userId()); + + // 2. 기록 조회 및 검증 + Record record = recordCommandPort.getByIdOrThrow(command.recordId()); + // 2-1. 기록 삭제 권한 검증 + record.validateDeletable(command.userId(),command.roomId()); + + // 3. 기록 삭제 + // 3-1. 기록 게시물 댓글 삭제 + commentCommandPort.softDeleteAllByPostId(command.recordId()); + // 3-2. 피드 게시물 좋아요 삭제 + postLikeCommandPort.deleteAllByPostId(command.recordId()); + // 3-3. 기록 삭제 + recordCommandPort.delete(record); + + return command.roomId(); + } +} diff --git a/src/main/java/konkuk/thip/record/domain/Record.java b/src/main/java/konkuk/thip/record/domain/Record.java index 77454bb56..8856140d9 100644 --- a/src/main/java/konkuk/thip/record/domain/Record.java +++ b/src/main/java/konkuk/thip/record/domain/Record.java @@ -96,4 +96,22 @@ private void checkCommentCountNotUnderflow() { throw new InvalidStateException(COMMENT_COUNT_UNDERFLOW); } } -} + + private void validateCreator(Long userId) { + if (!this.creatorId.equals(userId)) { + throw new InvalidStateException(RECORD_ACCESS_FORBIDDEN, new IllegalArgumentException("기록 작성자만 기록을 수정/삭제할 수 있습니다.")); + } + } + + public void validateDeletable(Long userId,Long roomId) { + validateRoomId(roomId); + validateCreator(userId); + } + + private void validateRoomId(Long roomId) { + if (!this.roomId.equals(roomId)) { + throw new InvalidStateException(RECORD_ACCESS_FORBIDDEN, new IllegalArgumentException("기록이 해당 방에 속하지 않습니다.")); + } + } + +} \ No newline at end of file diff --git a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java index 2c6592c00..6dec59d60 100644 --- a/src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java +++ b/src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java @@ -20,7 +20,6 @@ import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -35,7 +34,8 @@ import static konkuk.thip.common.entity.StatusType.INACTIVE; import static konkuk.thip.common.post.PostType.FEED; -import static konkuk.thip.feed.domain.Tag.*; +import static konkuk.thip.feed.domain.Tag.FOREIGN_NOVEL; +import static konkuk.thip.feed.domain.Tag.KOREAN_NOVEL; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -43,6 +43,7 @@ @SpringBootTest @ActiveProfiles("test") +@Transactional @AutoConfigureMockMvc(addFilters = false) @DisplayName("[통합] 피드 삭제 api 통합 테스트") class FeedDeleteAPITest { @@ -92,22 +93,6 @@ void setUp() { commentJpaRepository.save(comment); } - @AfterEach - @Transactional - void tearDown() { - postLikeJpaRepository.deleteAllInBatch(); - savedFeedJpaRepository.deleteAllInBatch(); - commentLikeJpaRepository.deleteAllInBatch(); - commentJpaRepository.deleteAllInBatch(); - contentJpaRepository.deleteAllInBatch(); - feedTagJpaRepository.deleteAllInBatch(); - feedJpaRepository.deleteAllInBatch(); - bookJpaRepository.deleteAllInBatch(); - userJpaRepository.deleteAllInBatch(); - tagJpaRepository.deleteAllInBatch(); - categoryJpaRepository.deleteAllInBatch(); - aliasJpaRepository.deleteAllInBatch(); - } @Test @DisplayName("피드를 삭제하면 [soft delete]되고, 연관된 피드 태그 연관관계, 콘텐츠(사진), 댓글, 댓글 좋아요, 피드 저장관계도 모두 삭제된다") diff --git a/src/test/java/konkuk/thip/record/adapter/in/web/RecordDeleteAPITest.java b/src/test/java/konkuk/thip/record/adapter/in/web/RecordDeleteAPITest.java new file mode 100644 index 000000000..fe78c1564 --- /dev/null +++ b/src/test/java/konkuk/thip/record/adapter/in/web/RecordDeleteAPITest.java @@ -0,0 +1,107 @@ +package konkuk.thip.record.adapter.in.web; + +import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; +import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; +import konkuk.thip.comment.adapter.out.jpa.CommentJpaEntity; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentJpaRepository; +import konkuk.thip.comment.adapter.out.persistence.repository.CommentLikeJpaRepository; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.post.adapter.out.persistence.PostLikeJpaRepository; +import konkuk.thip.record.adapter.out.jpa.RecordJpaEntity; +import konkuk.thip.record.adapter.out.persistence.repository.RecordJpaRepository; +import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantRole; +import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +import static konkuk.thip.common.entity.StatusType.INACTIVE; +import static konkuk.thip.common.post.PostType.RECORD; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] 기록 삭제 api 통합 테스트") +class RecordDeleteAPITest { + + @Autowired + private MockMvc mockMvc; + + @Autowired private AliasJpaRepository aliasJpaRepository; + @Autowired private UserJpaRepository userJpaRepository; + @Autowired private CategoryJpaRepository categoryJpaRepository; + @Autowired private BookJpaRepository bookJpaRepository; + @Autowired private CommentJpaRepository commentJpaRepository; + @Autowired private CommentLikeJpaRepository commentLikeJpaRepository; + @Autowired private PostLikeJpaRepository postLikeJpaRepository; + @Autowired private RecordJpaRepository recordJpaRepository; + @Autowired private RoomJpaRepository roomJpaRepository; + @Autowired private RoomParticipantJpaRepository roomParticipantJpaRepository; + + private AliasJpaEntity alias; + private UserJpaEntity user; + private CategoryJpaEntity category; + private BookJpaEntity book; + private CommentJpaEntity comment; + private RecordJpaEntity record; + private RoomJpaEntity room; + + @BeforeEach + void setUp() { + alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias()); + user = userJpaRepository.save(TestEntityFactory.createUser(alias)); + category = categoryJpaRepository.save(TestEntityFactory.createLiteratureCategory(alias)); + book = bookJpaRepository.save(TestEntityFactory.createBookWithISBN("9788954682152")); + room = roomJpaRepository.save(TestEntityFactory.createRoom(book,category)); + record = recordJpaRepository.save(TestEntityFactory.createRecord(user,room)); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, user, RoomParticipantRole.HOST, 0.0)); + postLikeJpaRepository.save(TestEntityFactory.createPostLike(user,record)); + comment = commentJpaRepository.save(TestEntityFactory.createComment(record, user, RECORD)); + commentLikeJpaRepository.save(TestEntityFactory.createCommentLike(comment,user)); + record.updateLikeCount(1); + record.updateCommentCount(1); + recordJpaRepository.save(record); + comment.updateLikeCount(1); + commentJpaRepository.save(comment); + } + + @Test + @DisplayName("기록을 삭제하면 [soft delete]되고, 연관된 댓글, 댓글 좋아요도 모두 삭제된다") + void deleteRecord_success() throws Exception { + + // when + mockMvc.perform(delete("/rooms/{roomId}/record/{recordId}", room.getRoomId(), record.getPostId()) + .requestAttr("userId", user.getUserId())) + .andExpect(status().isOk()); + + + // then: 1) 기록 soft delete (status=INACTIVE) + assertThat(recordJpaRepository.findByPostIdAndStatus(record.getPostId(), INACTIVE)).isPresent(); + + // 2) 댓글 삭제 soft delete + assertThat(commentJpaRepository.findByCommentIdAndStatus(comment.getCommentId(),INACTIVE)).isPresent(); + + // 3) 댓글 좋아요 삭제 + long commentLikeCountAfter = commentLikeJpaRepository.count(); + assertThat(commentLikeCountAfter).isEqualTo(0); + } +} diff --git a/src/test/java/konkuk/thip/record/domain/RecordTest.java b/src/test/java/konkuk/thip/record/domain/RecordTest.java index 4dba6a5b0..a8fefdd5f 100644 --- a/src/test/java/konkuk/thip/record/domain/RecordTest.java +++ b/src/test/java/konkuk/thip/record/domain/RecordTest.java @@ -6,8 +6,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static konkuk.thip.common.exception.code.ErrorCode.COMMENT_COUNT_UNDERFLOW; -import static konkuk.thip.common.exception.code.ErrorCode.POST_LIKE_COUNT_UNDERFLOW; +import static konkuk.thip.common.exception.code.ErrorCode.*; import static org.junit.jupiter.api.Assertions.*; @DisplayName("[단위] Record 도메인 테스트") @@ -21,6 +20,10 @@ void setUp() { } private final Long CREATOR_ID = 1L; + private final Long OTHER_USER_ID = 2L; + + private final Long ROOM_ID = 1L; + private final Long OTHER_ROOM_ID = 2L; private Record createWithCommentRecord() { return Record.builder() @@ -31,7 +34,7 @@ private Record createWithCommentRecord() { .isOverview(false) .likeCount(0) .commentCount(1) - .roomId(100L) + .roomId(ROOM_ID) .build(); } @@ -176,4 +179,31 @@ void updateLikeCount_likeFalse_underflow_throws() { assertEquals(POST_LIKE_COUNT_UNDERFLOW, ex.getErrorCode()); } + @Test + @DisplayName("validateDeletable: 작성자가 아닌 경우 기록을 삭제하려고 하면 InvalidStateException이 발생한다.") + void validateDeletable_byNonCreator_throws(){ + Record record = createWithCommentRecord(); + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> record.validateDeletable(OTHER_USER_ID,ROOM_ID)); + + assertEquals(RECORD_ACCESS_FORBIDDEN, ex.getErrorCode()); + } + + @Test + @DisplayName("validateDeletable: 전달된 roomId가 기록의 roomId가 일치 하지않은 경우 기록을 삭제하려고 하면 InvalidStateException이 발생한다.") + void validateDeletable_byOtherRoomId_throws(){ + Record record = createWithCommentRecord(); + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> record.validateDeletable(CREATOR_ID,OTHER_ROOM_ID)); + + assertEquals(RECORD_ACCESS_FORBIDDEN, ex.getErrorCode()); + } + + @Test + @DisplayName("validateDeletable: 피드의 작성자면서, 전달된 roomId가 기록의 roomId와 일치할 경우 기록을 삭제 할 수 있다.") + void validateDeletable_byCreator_byRoomId_Success(){ + Record record = createWithCommentRecord(); + assertDoesNotThrow(() -> record.validateDeletable(CREATOR_ID,ROOM_ID)); + } + } \ No newline at end of file