diff --git a/src/main/java/com/example/solidconnection/post/controller/PostController.java b/src/main/java/com/example/solidconnection/post/controller/PostController.java index 05cdfc574..c3ff3ce3a 100644 --- a/src/main/java/com/example/solidconnection/post/controller/PostController.java +++ b/src/main/java/com/example/solidconnection/post/controller/PostController.java @@ -8,7 +8,9 @@ import com.example.solidconnection.post.dto.PostLikeResponse; import com.example.solidconnection.post.dto.PostUpdateRequest; import com.example.solidconnection.post.dto.PostUpdateResponse; -import com.example.solidconnection.post.service.PostService; +import com.example.solidconnection.post.service.PostCommandService; +import com.example.solidconnection.post.service.PostLikeService; +import com.example.solidconnection.post.service.PostQueryService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -32,7 +34,9 @@ @RequestMapping("/communities") public class PostController { - private final PostService postService; + private final PostQueryService postQueryService; + private final PostCommandService postCommandService; + private final PostLikeService postLikeService; @PostMapping(value = "/{code}/posts") public ResponseEntity createPost( @@ -44,7 +48,7 @@ public ResponseEntity createPost( if (imageFile == null) { imageFile = Collections.emptyList(); } - PostCreateResponse post = postService + PostCreateResponse post = postCommandService .createPost(principal.getName(), code, postCreateRequest, imageFile); return ResponseEntity.ok().body(post); } @@ -60,19 +64,18 @@ public ResponseEntity updatePost( if (imageFile == null) { imageFile = Collections.emptyList(); } - PostUpdateResponse postUpdateResponse = postService + PostUpdateResponse postUpdateResponse = postCommandService .updatePost(principal.getName(), code, postId, postUpdateRequest, imageFile); return ResponseEntity.ok().body(postUpdateResponse); } - @GetMapping("/{code}/posts/{post_id}") public ResponseEntity findPostById( Principal principal, @PathVariable("code") String code, @PathVariable("post_id") Long postId) { - PostFindResponse postFindResponse = postService + PostFindResponse postFindResponse = postQueryService .findPostById(principal.getName(), code, postId); return ResponseEntity.ok().body(postFindResponse); } @@ -83,7 +86,7 @@ public ResponseEntity deletePostById( @PathVariable("code") String code, @PathVariable("post_id") Long postId) { - PostDeleteResponse postDeleteResponse = postService.deletePostById(principal.getName(), code, postId); + PostDeleteResponse postDeleteResponse = postCommandService.deletePostById(principal.getName(), code, postId); return ResponseEntity.ok().body(postDeleteResponse); } @@ -94,7 +97,7 @@ public ResponseEntity likePost( @PathVariable("post_id") Long postId ) { - PostLikeResponse postLikeResponse = postService.likePost(principal.getName(), code, postId); + PostLikeResponse postLikeResponse = postLikeService.likePost(principal.getName(), code, postId); return ResponseEntity.ok().body(postLikeResponse); } @@ -105,7 +108,7 @@ public ResponseEntity dislikePost( @PathVariable("post_id") Long postId ) { - PostDislikeResponse postDislikeResponse = postService.dislikePost(principal.getName(), code, postId); + PostDislikeResponse postDislikeResponse = postLikeService.dislikePost(principal.getName(), code, postId); return ResponseEntity.ok().body(postDislikeResponse); } } diff --git a/src/main/java/com/example/solidconnection/post/service/PostService.java b/src/main/java/com/example/solidconnection/post/service/PostCommandService.java similarity index 60% rename from src/main/java/com/example/solidconnection/post/service/PostService.java rename to src/main/java/com/example/solidconnection/post/service/PostCommandService.java index d31cfb97a..7b0c4f937 100644 --- a/src/main/java/com/example/solidconnection/post/service/PostService.java +++ b/src/main/java/com/example/solidconnection/post/service/PostCommandService.java @@ -1,30 +1,20 @@ package com.example.solidconnection.post.service; import com.example.solidconnection.board.domain.Board; -import com.example.solidconnection.board.dto.PostFindBoardResponse; import com.example.solidconnection.board.repository.BoardRepository; -import com.example.solidconnection.comment.dto.PostFindCommentResponse; -import com.example.solidconnection.comment.service.CommentService; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.entity.PostImage; import com.example.solidconnection.post.domain.Post; -import com.example.solidconnection.post.domain.PostLike; import com.example.solidconnection.post.dto.PostCreateRequest; import com.example.solidconnection.post.dto.PostCreateResponse; import com.example.solidconnection.post.dto.PostDeleteResponse; -import com.example.solidconnection.post.dto.PostDislikeResponse; -import com.example.solidconnection.post.dto.PostFindPostImageResponse; -import com.example.solidconnection.post.dto.PostFindResponse; -import com.example.solidconnection.post.dto.PostLikeResponse; import com.example.solidconnection.post.dto.PostUpdateRequest; import com.example.solidconnection.post.dto.PostUpdateResponse; -import com.example.solidconnection.post.repository.PostLikeRepository; import com.example.solidconnection.post.repository.PostRepository; import com.example.solidconnection.s3.S3Service; import com.example.solidconnection.s3.UploadedFileUrlResponse; import com.example.solidconnection.service.RedisService; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.type.BoardCode; import com.example.solidconnection.type.ImgType; @@ -33,7 +23,6 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.EnumUtils; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -41,72 +30,24 @@ import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; -import static com.example.solidconnection.custom.exception.ErrorCode.DUPLICATE_POST_LIKE; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_BOARD_CODE; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_CATEGORY; @Service @RequiredArgsConstructor -public class PostService { +public class PostCommandService { private final PostRepository postRepository; private final SiteUserRepository siteUserRepository; private final BoardRepository boardRepository; private final S3Service s3Service; - private final CommentService commentService; private final RedisService redisService; private final RedisUtils redisUtils; - private final PostLikeRepository postLikeRepository; - - private String validateCode(String code) { - try { - return String.valueOf(BoardCode.valueOf(code)); - } catch (IllegalArgumentException ex) { - throw new CustomException(INVALID_BOARD_CODE); - } - } - - private void validateOwnership(Post post, String email) { - if (!post.getSiteUser().getEmail().equals(email)) { - throw new CustomException(INVALID_POST_ACCESS); - } - } - - private void validateFileSize(List imageFile) { - if (imageFile.isEmpty()) { - return; - } - if (imageFile.size() > 5) { - throw new CustomException(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES); - } - } - - private void validateQuestion(Post post) { - if (post.getIsQuestion()) { - throw new CustomException(CAN_NOT_DELETE_OR_UPDATE_QUESTION); - } - } - - private void validatePostCategory(String category) { - if (!EnumUtils.isValidEnum(PostCategory.class, category) || category.equals(PostCategory.전체.toString())) { - throw new CustomException(INVALID_POST_CATEGORY); - } - } - - private Boolean getIsOwner(Post post, String email) { - return post.getSiteUser().getEmail().equals(email); - } - - private Boolean getIsLiked(Post post, SiteUser siteUser) { - return postLikeRepository.findPostLikeByPostAndSiteUser(post, siteUser) - .isPresent(); - } @Transactional public PostCreateResponse createPost(String email, String code, PostCreateRequest postCreateRequest, List imageFile) { - // 유효성 검증 String boardCode = validateCode(code); validatePostCategory(postCreateRequest.postCategory()); @@ -126,7 +67,6 @@ public PostCreateResponse createPost(String email, String code, PostCreateReques @Transactional public PostUpdateResponse updatePost(String email, String code, Long postId, PostUpdateRequest postUpdateRequest, List imageFile) { - // 유효성 검증 String boardCode = validateCode(code); Post post = postRepository.getById(postId); @@ -155,40 +95,8 @@ private void savePostImages(List imageFile, Post post) { } } - private void removePostImages(Post post) { - for (PostImage postImage : post.getPostImageList()) { - s3Service.deletePostImage(postImage.getUrl()); - } - post.getPostImageList().clear(); - } - - @Transactional(readOnly = true) - public PostFindResponse findPostById(String email, String code, Long postId) { - - String boardCode = validateCode(code); - - Post post = postRepository.getByIdUsingEntityGraph(postId); - SiteUser siteUser = siteUserRepository.getByEmail(email); - Boolean isOwner = getIsOwner(post, email); - Boolean isLiked = getIsLiked(post, siteUser); - - PostFindBoardResponse boardPostFindResultDTO = PostFindBoardResponse.from(post.getBoard()); - PostFindSiteUserResponse siteUserPostFindResultDTO = PostFindSiteUserResponse.from(post.getSiteUser()); - List postImageFindResultDTOList = PostFindPostImageResponse.from(post.getPostImageList()); - List commentFindResultDTOList = commentService.findCommentsByPostId(email, postId); - - // caching && 어뷰징 방지 - if (redisService.isPresent(redisUtils.getValidatePostViewCountRedisKey(email, postId))) { - redisService.increaseViewCount(redisUtils.getPostViewCountRedisKey(postId)); - } - - return PostFindResponse.from( - post, isOwner, isLiked, boardPostFindResultDTO, siteUserPostFindResultDTO, commentFindResultDTOList, postImageFindResultDTOList); - } - @Transactional public PostDeleteResponse deletePostById(String email, String code, Long postId) { - String boardCode = validateCode(code); Post post = postRepository.getById(postId); validateOwnership(post, email); @@ -203,40 +111,45 @@ public PostDeleteResponse deletePostById(String email, String code, Long postId) return new PostDeleteResponse(postId); } - @Transactional(isolation = Isolation.READ_COMMITTED) - public PostLikeResponse likePost(String email, String code, Long postId) { - - String boardCode = validateCode(code); - Post post = postRepository.getById(postId); - SiteUser siteUser = siteUserRepository.getByEmail(email); - validateDuplicatePostLike(post, siteUser); - - PostLike postLike = new PostLike(); - postLike.setPostAndSiteUser(post, siteUser); - postLikeRepository.save(postLike); - postRepository.increaseLikeCount(post.getId()); - - return PostLikeResponse.from(postRepository.getById(postId)); // 실시간성을 위한 재조회 + private String validateCode(String code) { + try { + return String.valueOf(BoardCode.valueOf(code)); + } catch (IllegalArgumentException ex) { + throw new CustomException(INVALID_BOARD_CODE); + } } - private void validateDuplicatePostLike(Post post, SiteUser siteUser) { - if (postLikeRepository.findPostLikeByPostAndSiteUser(post, siteUser).isPresent()) { - throw new CustomException(DUPLICATE_POST_LIKE); + private void validateOwnership(Post post, String email) { + if (!post.getSiteUser().getEmail().equals(email)) { + throw new CustomException(INVALID_POST_ACCESS); } } - @Transactional(isolation = Isolation.READ_COMMITTED) - public PostDislikeResponse dislikePost(String email, String code, Long postId) { + private void validateFileSize(List imageFile) { + if (imageFile.isEmpty()) { + return; + } + if (imageFile.size() > 5) { + throw new CustomException(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES); + } + } - String boardCode = validateCode(code); - Post post = postRepository.getById(postId); - SiteUser siteUser = siteUserRepository.getByEmail(email); + private void validateQuestion(Post post) { + if (post.getIsQuestion()) { + throw new CustomException(CAN_NOT_DELETE_OR_UPDATE_QUESTION); + } + } - PostLike postLike = postLikeRepository.getByPostAndSiteUser(post, siteUser); - postLike.resetPostAndSiteUser(); - postLikeRepository.deleteById(postLike.getId()); - postRepository.decreaseLikeCount(post.getId()); + private void validatePostCategory(String category) { + if (!EnumUtils.isValidEnum(PostCategory.class, category) || category.equals(PostCategory.전체.toString())) { + throw new CustomException(INVALID_POST_CATEGORY); + } + } - return PostDislikeResponse.from(postRepository.getById(postId)); // 실시간성을 위한 재조회 + private void removePostImages(Post post) { + for (PostImage postImage : post.getPostImageList()) { + s3Service.deletePostImage(postImage.getUrl()); + } + post.getPostImageList().clear(); } } diff --git a/src/main/java/com/example/solidconnection/post/service/PostLikeService.java b/src/main/java/com/example/solidconnection/post/service/PostLikeService.java new file mode 100644 index 000000000..8a72d5f9f --- /dev/null +++ b/src/main/java/com/example/solidconnection/post/service/PostLikeService.java @@ -0,0 +1,71 @@ +package com.example.solidconnection.post.service; + +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.post.domain.Post; +import com.example.solidconnection.post.domain.PostLike; +import com.example.solidconnection.post.dto.PostDislikeResponse; +import com.example.solidconnection.post.dto.PostLikeResponse; +import com.example.solidconnection.post.repository.PostLikeRepository; +import com.example.solidconnection.post.repository.PostRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.type.BoardCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +import static com.example.solidconnection.custom.exception.ErrorCode.DUPLICATE_POST_LIKE; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_BOARD_CODE; + +@Service +@RequiredArgsConstructor +public class PostLikeService { + + private final PostRepository postRepository; + private final SiteUserRepository siteUserRepository; + private final PostLikeRepository postLikeRepository; + + @Transactional(isolation = Isolation.READ_COMMITTED) + public PostLikeResponse likePost(String email, String code, Long postId) { + String boardCode = validateCode(code); + Post post = postRepository.getById(postId); + SiteUser siteUser = siteUserRepository.getByEmail(email); + validateDuplicatePostLike(post, siteUser); + + PostLike postLike = new PostLike(); + postLike.setPostAndSiteUser(post, siteUser); + postLikeRepository.save(postLike); + postRepository.increaseLikeCount(post.getId()); + + return PostLikeResponse.from(postRepository.getById(postId)); // 실시간성을 위한 재조회 + } + + @Transactional(isolation = Isolation.READ_COMMITTED) + public PostDislikeResponse dislikePost(String email, String code, Long postId) { + String boardCode = validateCode(code); + Post post = postRepository.getById(postId); + SiteUser siteUser = siteUserRepository.getByEmail(email); + + PostLike postLike = postLikeRepository.getByPostAndSiteUser(post, siteUser); + postLike.resetPostAndSiteUser(); + postLikeRepository.deleteById(postLike.getId()); + postRepository.decreaseLikeCount(post.getId()); + + return PostDislikeResponse.from(postRepository.getById(postId)); // 실시간성을 위한 재조회 + } + + private String validateCode(String code) { + try { + return String.valueOf(BoardCode.valueOf(code)); + } catch (IllegalArgumentException ex) { + throw new CustomException(INVALID_BOARD_CODE); + } + } + + private void validateDuplicatePostLike(Post post, SiteUser siteUser) { + if (postLikeRepository.findPostLikeByPostAndSiteUser(post, siteUser).isPresent()) { + throw new CustomException(DUPLICATE_POST_LIKE); + } + } +} diff --git a/src/main/java/com/example/solidconnection/post/service/PostQueryService.java b/src/main/java/com/example/solidconnection/post/service/PostQueryService.java new file mode 100644 index 000000000..d53470124 --- /dev/null +++ b/src/main/java/com/example/solidconnection/post/service/PostQueryService.java @@ -0,0 +1,76 @@ +package com.example.solidconnection.post.service; + +import com.example.solidconnection.board.dto.PostFindBoardResponse; +import com.example.solidconnection.comment.dto.PostFindCommentResponse; +import com.example.solidconnection.comment.service.CommentService; +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.post.domain.Post; +import com.example.solidconnection.post.dto.PostFindPostImageResponse; +import com.example.solidconnection.post.dto.PostFindResponse; +import com.example.solidconnection.post.repository.PostLikeRepository; +import com.example.solidconnection.post.repository.PostRepository; +import com.example.solidconnection.service.RedisService; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.type.BoardCode; +import com.example.solidconnection.util.RedisUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_BOARD_CODE; + +@Service +@RequiredArgsConstructor +public class PostQueryService { + + private final PostRepository postRepository; + private final SiteUserRepository siteUserRepository; + private final CommentService commentService; + private final RedisService redisService; + private final RedisUtils redisUtils; + private final PostLikeRepository postLikeRepository; + + @Transactional(readOnly = true) + public PostFindResponse findPostById(String email, String code, Long postId) { + String boardCode = validateCode(code); + + Post post = postRepository.getByIdUsingEntityGraph(postId); + SiteUser siteUser = siteUserRepository.getByEmail(email); + Boolean isOwner = getIsOwner(post, email); + Boolean isLiked = getIsLiked(post, siteUser); + + PostFindBoardResponse boardPostFindResultDTO = PostFindBoardResponse.from(post.getBoard()); + PostFindSiteUserResponse siteUserPostFindResultDTO = PostFindSiteUserResponse.from(post.getSiteUser()); + List postImageFindResultDTOList = PostFindPostImageResponse.from(post.getPostImageList()); + List commentFindResultDTOList = commentService.findCommentsByPostId(email, postId); + + // caching && 어뷰징 방지 + if (redisService.isPresent(redisUtils.getValidatePostViewCountRedisKey(email, postId))) { + redisService.increaseViewCount(redisUtils.getPostViewCountRedisKey(postId)); + } + + return PostFindResponse.from( + post, isOwner, isLiked, boardPostFindResultDTO, siteUserPostFindResultDTO, commentFindResultDTOList, postImageFindResultDTOList); + } + + private String validateCode(String code) { + try { + return String.valueOf(BoardCode.valueOf(code)); + } catch (IllegalArgumentException ex) { + throw new CustomException(INVALID_BOARD_CODE); + } + } + + private Boolean getIsOwner(Post post, String email) { + return post.getSiteUser().getEmail().equals(email); + } + + private Boolean getIsLiked(Post post, SiteUser siteUser) { + return postLikeRepository.findPostLikeByPostAndSiteUser(post, siteUser) + .isPresent(); + } +} diff --git a/src/main/java/com/example/solidconnection/service/RedisService.java b/src/main/java/com/example/solidconnection/service/RedisService.java index 93a9de74f..36be7b66f 100644 --- a/src/main/java/com/example/solidconnection/service/RedisService.java +++ b/src/main/java/com/example/solidconnection/service/RedisService.java @@ -42,4 +42,8 @@ public boolean isPresent(String key) { return Boolean.TRUE.equals(redisTemplate.opsForValue() .setIfAbsent(key, "1", Long.parseLong(VALIDATE_VIEW_COUNT_TTL.getValue()), TimeUnit.SECONDS)); } + + public boolean isKeyExists(String key) { + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } } diff --git a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java index e553eb4bb..36bd91819 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java @@ -4,7 +4,8 @@ import com.example.solidconnection.board.repository.BoardRepository; import com.example.solidconnection.post.domain.Post; import com.example.solidconnection.post.repository.PostRepository; -import com.example.solidconnection.post.service.PostService; +import com.example.solidconnection.post.service.PostCommandService; +import com.example.solidconnection.post.service.PostLikeService; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -30,7 +31,7 @@ class PostLikeCountConcurrencyTest { @Autowired - private PostService postService; + private PostLikeService postLikeService; @Autowired private PostRepository postRepository; @Autowired @@ -118,8 +119,8 @@ private Post createPost(Board board, SiteUser siteUser) { String email = "email" + i; executorService.submit(() -> { try { - postService.likePost(email, board.getCode(), post.getId()); - postService.dislikePost(email, board.getCode(), post.getId()); + postLikeService.likePost(email, board.getCode(), post.getId()); + postLikeService.dislikePost(email, board.getCode(), post.getId()); } finally { doneSignal.countDown(); } @@ -135,5 +136,4 @@ private Post createPost(Board board, SiteUser siteUser) { assertEquals(likeCount, postRepository.getById(post.getId()).getLikeCount()); } - } diff --git a/src/test/java/com/example/solidconnection/post/service/PostCommandServiceTest.java b/src/test/java/com/example/solidconnection/post/service/PostCommandServiceTest.java new file mode 100644 index 000000000..eb1b2b652 --- /dev/null +++ b/src/test/java/com/example/solidconnection/post/service/PostCommandServiceTest.java @@ -0,0 +1,373 @@ +package com.example.solidconnection.post.service; + +import com.example.solidconnection.board.domain.Board; +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.entity.PostImage; +import com.example.solidconnection.post.domain.Post; +import com.example.solidconnection.post.dto.PostCreateRequest; +import com.example.solidconnection.post.dto.PostCreateResponse; +import com.example.solidconnection.post.dto.PostDeleteResponse; +import com.example.solidconnection.post.dto.PostUpdateRequest; +import com.example.solidconnection.post.dto.PostUpdateResponse; +import com.example.solidconnection.post.repository.PostRepository; +import com.example.solidconnection.repositories.PostImageRepository; +import com.example.solidconnection.s3.S3Service; +import com.example.solidconnection.s3.UploadedFileUrlResponse; +import com.example.solidconnection.service.RedisService; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.type.ImgType; +import com.example.solidconnection.type.PostCategory; +import com.example.solidconnection.util.RedisUtils; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; +import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_CATEGORY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.eq; +import static org.mockito.BDDMockito.then; + +@DisplayName("게시글 생성/수정/삭제 서비스 테스트") +class PostCommandServiceTest extends BaseIntegrationTest { + + @Autowired + private PostCommandService postCommandService; + + @MockBean + private S3Service s3Service; + + @Autowired + private RedisService redisService; + + @Autowired + private RedisUtils redisUtils; + + @Autowired + private PostRepository postRepository; + + @Autowired + private PostImageRepository postImageRepository; + + @Nested + class 게시글_생성_테스트 { + + @Test + @Transactional + void 게시글을_성공적으로_생성한다() { + // given + PostCreateRequest request = createPostCreateRequest(PostCategory.자유.name()); + List imageFiles = List.of(createImageFile()); + String expectedImageUrl = "test-image-url"; + given(s3Service.uploadFiles(any(), eq(ImgType.COMMUNITY))) + .willReturn(List.of(new UploadedFileUrlResponse(expectedImageUrl))); + + // when + PostCreateResponse response = postCommandService.createPost( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + request, + imageFiles + ); + + // then + Post savedPost = postRepository.findById(response.id()).orElseThrow(); + assertAll( + () -> assertThat(response.id()).isEqualTo(savedPost.getId()), + () -> assertThat(savedPost.getTitle()).isEqualTo(request.title()), + () -> assertThat(savedPost.getContent()).isEqualTo(request.content()), + () -> assertThat(savedPost.getIsQuestion()).isEqualTo(request.isQuestion()), + () -> assertThat(savedPost.getCategory().name()).isEqualTo(request.postCategory()), + () -> assertThat(savedPost.getBoard().getCode()).isEqualTo(자유게시판.getCode()), + () -> assertThat(savedPost.getPostImageList()).hasSize(imageFiles.size()), + () -> assertThat(savedPost.getPostImageList()) + .extracting(PostImage::getUrl) + .containsExactly(expectedImageUrl) + ); + } + + @Test + void 전체_카테고리로_생성하면_예외_응답을_반환한다() { + // given + PostCreateRequest request = createPostCreateRequest(PostCategory.전체.name()); + List imageFiles = List.of(); + + // when & then + assertThatThrownBy(() -> + postCommandService.createPost(테스트유저_1.getEmail(), 자유게시판.getCode(), request, imageFiles)) + .isInstanceOf(CustomException.class) + .hasMessage(INVALID_POST_CATEGORY.getMessage()); + } + + @Test + void 존재하지_않는_카테고리로_생성하면_예외_응답을_반환한다() { + // given + PostCreateRequest request = createPostCreateRequest("INVALID_CATEGORY"); + List imageFiles = List.of(); + + // when & then + assertThatThrownBy(() -> + postCommandService.createPost(테스트유저_1.getEmail(), 자유게시판.getCode(), request, imageFiles)) + .isInstanceOf(CustomException.class) + .hasMessage(INVALID_POST_CATEGORY.getMessage()); + } + + @Test + void 이미지를_5개_초과하여_업로드하면_예외_응답을_반환한다() { + // given + PostCreateRequest request = createPostCreateRequest(PostCategory.자유.name()); + List imageFiles = createSixImageFiles(); + + // when & then + assertThatThrownBy(() -> + postCommandService.createPost(테스트유저_1.getEmail(), 자유게시판.getCode(), request, imageFiles)) + .isInstanceOf(CustomException.class) + .hasMessage(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES.getMessage()); + } + } + + @Nested + class 게시글_수정_테스트 { + + @Test + @Transactional + void 게시글을_성공적으로_수정한다() { + // given + String originImageUrl = "origin-image-url"; + String expectedImageUrl = "update-image-url"; + Post testPost = createPost(자유게시판, 테스트유저_1, originImageUrl); + PostUpdateRequest request = createPostUpdateRequest(); + List imageFiles = List.of(createImageFile()); + + given(s3Service.uploadFiles(any(), eq(ImgType.COMMUNITY))) + .willReturn(List.of(new UploadedFileUrlResponse(expectedImageUrl))); + + // when + PostUpdateResponse response = postCommandService.updatePost( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId(), + request, + imageFiles + ); + + // then + Post updatedPost = postRepository.findById(response.id()).orElseThrow(); + assertAll( + () -> assertThat(updatedPost.getTitle()).isEqualTo(request.title()), + () -> assertThat(updatedPost.getContent()).isEqualTo(request.content()), + () -> assertThat(updatedPost.getCategory().name()).isEqualTo(request.postCategory()), + () -> assertThat(updatedPost.getPostImageList()).hasSize(imageFiles.size()), + () -> assertThat(updatedPost.getPostImageList()) + .extracting(PostImage::getUrl) + .containsExactly(expectedImageUrl) + ); + then(s3Service).should().deletePostImage(originImageUrl); + } + + @Test + void 다른_사용자의_게시글을_수정하면_예외_응답을_반환한다() { + // given + Post testPost = createPost(자유게시판, 테스트유저_1, "origin-image-url"); + PostUpdateRequest request = createPostUpdateRequest(); + List imageFiles = List.of(); + + // when & then + assertThatThrownBy(() -> + postCommandService.updatePost( + 테스트유저_2.getEmail(), + 자유게시판.getCode(), + testPost.getId(), + request, + imageFiles + )) + .isInstanceOf(CustomException.class) + .hasMessage(INVALID_POST_ACCESS.getMessage()); + } + + @Test + void 질문_게시글을_수정하면_예외_응답을_반환한다() { + // given + Post testPost = createQuestionPost(자유게시판, 테스트유저_1, "origin-image-url"); + PostUpdateRequest request = createPostUpdateRequest(); + List imageFiles = List.of(); + + // when & then + assertThatThrownBy(() -> + postCommandService.updatePost( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId(), + request, + imageFiles + )) + .isInstanceOf(CustomException.class) + .hasMessage(CAN_NOT_DELETE_OR_UPDATE_QUESTION.getMessage()); + } + + @Test + void 이미지를_5개_초과하여_수정하면_예외_응답을_반환한다() { + // given + Post testPost = createPost(자유게시판, 테스트유저_1, "origin-image-url"); + PostUpdateRequest request = createPostUpdateRequest(); + List imageFiles = createSixImageFiles(); + + // when & then + assertThatThrownBy(() -> + postCommandService.updatePost( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId(), + request, + imageFiles + )) + .isInstanceOf(CustomException.class) + .hasMessage(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES.getMessage()); + } + } + + @Nested + class 게시글_삭제_테스트 { + + @Test + void 게시글을_성공적으로_삭제한다() { + // given + String originImageUrl = "origin-image-url"; + Post testPost = createPost(자유게시판, 테스트유저_1, originImageUrl); + String viewCountKey = redisUtils.getPostViewCountRedisKey(testPost.getId()); + redisService.increaseViewCount(viewCountKey); + + // when + PostDeleteResponse response = postCommandService.deletePostById( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId() + ); + + // then + assertAll( + () -> assertThat(response.id()).isEqualTo(testPost.getId()), + () -> assertThat(postRepository.findById(testPost.getId())).isEmpty(), + () -> assertThat(redisService.isKeyExists(viewCountKey)).isFalse() + ); + then(s3Service).should().deletePostImage(originImageUrl); + } + + @Test + void 다른_사용자의_게시글을_삭제하면_예외_응답을_반환한다() { + // given + Post testPost = createPost(자유게시판, 테스트유저_1, "origin-image-url"); + + // when & then + assertThatThrownBy(() -> + postCommandService.deletePostById( + 테스트유저_2.getEmail(), + 자유게시판.getCode(), + testPost.getId() + )) + .isInstanceOf(CustomException.class) + .hasMessage(INVALID_POST_ACCESS.getMessage()); + } + + @Test + void 질문_게시글을_삭제하면_예외_응답을_반환한다() { + // given + Post testPost = createQuestionPost(자유게시판, 테스트유저_1, "origin-image-url"); + + // when & then + assertThatThrownBy(() -> + postCommandService.deletePostById( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId() + )) + .isInstanceOf(CustomException.class) + .hasMessage(CAN_NOT_DELETE_OR_UPDATE_QUESTION.getMessage()); + } + } + + private PostCreateRequest createPostCreateRequest(String category) { + return new PostCreateRequest( + category, + "테스트 제목", + "테스트 내용", + false + ); + } + + private MockMultipartFile createImageFile() { + return new MockMultipartFile( + "image", + "test.jpg", + "image/jpeg", + "test image content".getBytes() + ); + } + + private List createSixImageFiles() { + return List.of( + createImageFile(), + createImageFile(), + createImageFile(), + createImageFile(), + createImageFile(), + createImageFile() + ); + } + + private Post createPost(Board board, SiteUser siteUser, String originImageUrl) { + Post post = new Post( + "원본 제목", + "원본 내용", + false, + 0L, + 0L, + PostCategory.자유 + ); + post.setBoardAndSiteUser(board, siteUser); + Post savedPost = postRepository.save(post); + PostImage postImage = new PostImage(originImageUrl); + postImage.setPost(savedPost); + postImageRepository.save(postImage); + return savedPost; + } + + private Post createQuestionPost(Board board, SiteUser siteUser, String originImageUrl) { + Post post = new Post( + "질문 제목", + "질문 내용", + true, + 0L, + 0L, + PostCategory.질문 + ); + post.setBoardAndSiteUser(board, siteUser); + Post savedPost = postRepository.save(post); + PostImage postImage = new PostImage(originImageUrl); + postImage.setPost(savedPost); + postImageRepository.save(postImage); + return savedPost; + } + + private PostUpdateRequest createPostUpdateRequest() { + return new PostUpdateRequest( + PostCategory.자유.name(), + "수정된 제목", + "수정된 내용" + ); + } +} diff --git a/src/test/java/com/example/solidconnection/post/service/PostLikeServiceTest.java b/src/test/java/com/example/solidconnection/post/service/PostLikeServiceTest.java new file mode 100644 index 000000000..9fe6a2704 --- /dev/null +++ b/src/test/java/com/example/solidconnection/post/service/PostLikeServiceTest.java @@ -0,0 +1,136 @@ +package com.example.solidconnection.post.service; + +import com.example.solidconnection.board.domain.Board; +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.post.domain.Post; +import com.example.solidconnection.post.dto.PostDislikeResponse; +import com.example.solidconnection.post.dto.PostLikeResponse; +import com.example.solidconnection.post.repository.PostLikeRepository; +import com.example.solidconnection.post.repository.PostRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.type.PostCategory; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static com.example.solidconnection.custom.exception.ErrorCode.DUPLICATE_POST_LIKE; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_LIKE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("게시글 좋아요 서비스 테스트") +class PostLikeServiceTest extends BaseIntegrationTest { + + @Autowired + private PostLikeService postLikeService; + + @Autowired + private PostRepository postRepository; + + @Autowired + private PostLikeRepository postLikeRepository; + + @Nested + class 게시글_좋아요_테스트 { + + @Test + void 게시글을_성공적으로_좋아요한다() { + // given + Post testPost = createPost(자유게시판, 테스트유저_1); + long beforeLikeCount = testPost.getLikeCount(); + + // when + PostLikeResponse response = postLikeService.likePost( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId() + ); + + // then + Post likedPost = postRepository.findById(testPost.getId()).orElseThrow(); + assertAll( + () -> assertThat(response.likeCount()).isEqualTo(beforeLikeCount + 1), + () -> assertThat(response.isLiked()).isTrue(), + () -> assertThat(likedPost.getLikeCount()).isEqualTo(beforeLikeCount + 1), + () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUser(likedPost, 테스트유저_1)).isPresent() + ); + } + + @Test + void 이미_좋아요한_게시글을_다시_좋아요하면_예외_응답을_반환한다() { + // given + Post testPost = createPost(자유게시판, 테스트유저_1); + postLikeService.likePost(테스트유저_1.getEmail(), 자유게시판.getCode(), testPost.getId()); + + // when & then + assertThatThrownBy(() -> + postLikeService.likePost( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId() + )) + .isInstanceOf(CustomException.class) + .hasMessage(DUPLICATE_POST_LIKE.getMessage()); + } + } + + @Nested + class 게시글_좋아요_취소_테스트 { + + @Test + void 게시글_좋아요를_성공적으로_취소한다() { + // given + Post testPost = createPost(자유게시판, 테스트유저_1); + PostLikeResponse beforeResponse = postLikeService.likePost(테스트유저_1.getEmail(), 자유게시판.getCode(), testPost.getId()); + long beforeLikeCount = beforeResponse.likeCount(); + + // when + PostDislikeResponse response = postLikeService.dislikePost( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId() + ); + + // then + Post unlikedPost = postRepository.findById(testPost.getId()).orElseThrow(); + assertAll( + () -> assertThat(response.likeCount()).isEqualTo(beforeLikeCount - 1), + () -> assertThat(response.isLiked()).isFalse(), + () -> assertThat(unlikedPost.getLikeCount()).isEqualTo(beforeLikeCount - 1), + () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUser(unlikedPost, 테스트유저_1)).isEmpty() + ); + } + + @Test + void 좋아요하지_않은_게시글을_좋아요_취소하면_예외_응답을_반환한다() { + // given + Post testPost = createPost(자유게시판, 테스트유저_1); + + // when & then + assertThatThrownBy(() -> + postLikeService.dislikePost( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId() + )) + .isInstanceOf(CustomException.class) + .hasMessage(INVALID_POST_LIKE.getMessage()); + } + } + + private Post createPost(Board board, SiteUser siteUser) { + Post post = new Post( + "테스트 제목", + "테스트 내용", + false, + 0L, + 0L, + PostCategory.자유 + ); + post.setBoardAndSiteUser(board, siteUser); + return postRepository.save(post); + } +} diff --git a/src/test/java/com/example/solidconnection/post/service/PostQueryServiceTest.java b/src/test/java/com/example/solidconnection/post/service/PostQueryServiceTest.java new file mode 100644 index 000000000..7ec36b0df --- /dev/null +++ b/src/test/java/com/example/solidconnection/post/service/PostQueryServiceTest.java @@ -0,0 +1,127 @@ +package com.example.solidconnection.post.service; + +import com.example.solidconnection.board.domain.Board; +import com.example.solidconnection.comment.domain.Comment; +import com.example.solidconnection.comment.dto.PostFindCommentResponse; +import com.example.solidconnection.comment.repository.CommentRepository; +import com.example.solidconnection.entity.PostImage; +import com.example.solidconnection.post.domain.Post; +import com.example.solidconnection.post.dto.PostFindPostImageResponse; +import com.example.solidconnection.post.dto.PostFindResponse; +import com.example.solidconnection.post.repository.PostRepository; +import com.example.solidconnection.repositories.PostImageRepository; +import com.example.solidconnection.service.RedisService; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.type.PostCategory; +import com.example.solidconnection.util.RedisUtils; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("게시글 조회 서비스 테스트") +class PostQueryServiceTest extends BaseIntegrationTest { + + @Autowired + private PostQueryService postQueryService; + + @Autowired + private RedisService redisService; + + @Autowired + private RedisUtils redisUtils; + + @Autowired + private PostRepository postRepository; + + @Autowired + private CommentRepository commentRepository; + + @Autowired + private PostImageRepository postImageRepository; + + @Test + void 게시글을_성공적으로_조회한다() { + // given + String expectedImageUrl = "test-image-url"; + List imageUrls = List.of(expectedImageUrl); + Post testPost = createPost(자유게시판, 테스트유저_1, expectedImageUrl); + List comments = createComments(testPost, 테스트유저_1, List.of("첫번째 댓글", "두번째 댓글")); + + String validateKey = redisUtils.getValidatePostViewCountRedisKey(테스트유저_1.getEmail(), testPost.getId()); + String viewCountKey = redisUtils.getPostViewCountRedisKey(testPost.getId()); + + // when + PostFindResponse response = postQueryService.findPostById( + 테스트유저_1.getEmail(), + 자유게시판.getCode(), + testPost.getId() + ); + + // then + assertAll( + () -> assertThat(response.id()).isEqualTo(testPost.getId()), + () -> assertThat(response.title()).isEqualTo(testPost.getTitle()), + () -> assertThat(response.content()).isEqualTo(testPost.getContent()), + () -> assertThat(response.isQuestion()).isEqualTo(testPost.getIsQuestion()), + () -> assertThat(response.likeCount()).isEqualTo(testPost.getLikeCount()), + () -> assertThat(response.viewCount()).isEqualTo(testPost.getViewCount()), + () -> assertThat(response.postCategory()).isEqualTo(String.valueOf(testPost.getCategory())), + + () -> assertThat(response.postFindBoardResponse().code()).isEqualTo(자유게시판.getCode()), + () -> assertThat(response.postFindBoardResponse().koreanName()).isEqualTo(자유게시판.getKoreanName()), + + () -> assertThat(response.postFindSiteUserResponse().id()).isEqualTo(테스트유저_1.getId()), + () -> assertThat(response.postFindSiteUserResponse().nickname()).isEqualTo(테스트유저_1.getNickname()), + () -> assertThat(response.postFindSiteUserResponse().profileImageUrl()).isEqualTo(테스트유저_1.getProfileImageUrl()), + + () -> assertThat(response.postFindPostImageResponses()) + .hasSize(imageUrls.size()) + .extracting(PostFindPostImageResponse::url) + .containsExactlyElementsOf(imageUrls), + + () -> assertThat(response.postFindCommentResponses()) + .hasSize(comments.size()) + .extracting(PostFindCommentResponse::content) + .containsExactlyElementsOf(comments.stream().map(Comment::getContent).toList()), + + () -> assertThat(response.isOwner()).isTrue(), + () -> assertThat(response.isLiked()).isFalse(), + + () -> assertThat(redisService.isKeyExists(viewCountKey)).isTrue(), + () -> assertThat(redisService.isKeyExists(validateKey)).isTrue() + ); + } + + private Post createPost(Board board, SiteUser siteUser, String originImageUrl) { + Post post = new Post( + "원본 제목", + "원본 내용", + false, + 0L, + 0L, + PostCategory.자유 + ); + post.setBoardAndSiteUser(board, siteUser); + Post savedPost = postRepository.save(post); + PostImage postImage = new PostImage(originImageUrl); + postImage.setPost(savedPost); + postImageRepository.save(postImage); + return savedPost; + } + + private List createComments(Post post, SiteUser siteUser, List contents) { + return contents.stream() + .map(content -> { + Comment comment = new Comment(content); + comment.setPostAndSiteUser(post, siteUser); + return commentRepository.save(comment); + }) + .toList(); + } +} diff --git a/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java b/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java index b1f7d9203..f588b87ae 100644 --- a/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java +++ b/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java @@ -1,12 +1,19 @@ package com.example.solidconnection.support.integration; +import com.example.solidconnection.board.domain.Board; +import com.example.solidconnection.board.repository.BoardRepository; import com.example.solidconnection.entity.Country; import com.example.solidconnection.entity.Region; import com.example.solidconnection.repositories.CountryRepository; import com.example.solidconnection.repositories.RegionRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.DatabaseClearExtension; import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.type.Gender; import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; import com.example.solidconnection.university.domain.LanguageRequirement; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; @@ -20,6 +27,10 @@ import java.util.HashSet; +import static com.example.solidconnection.type.BoardCode.AMERICAS; +import static com.example.solidconnection.type.BoardCode.ASIA; +import static com.example.solidconnection.type.BoardCode.EUROPE; +import static com.example.solidconnection.type.BoardCode.FREE; import static com.example.solidconnection.type.SemesterAvailableForDispatch.ONE_SEMESTER; import static com.example.solidconnection.type.TuitionFeeType.HOME_UNIVERSITY_PAYMENT; @@ -27,6 +38,9 @@ @ExtendWith(DatabaseClearExtension.class) public abstract class BaseIntegrationTest { + public static SiteUser 테스트유저_1; + public static SiteUser 테스트유저_2; + public static Region 영미권; public static Region 유럽; public static Region 아시아; @@ -57,6 +71,14 @@ public abstract class BaseIntegrationTest { public static UniversityInfoForApply 린츠_카톨릭대학_지원_정보; public static UniversityInfoForApply 메이지대학_지원_정보; + public static Board 미주권; + public static Board 아시아권; + public static Board 유럽권; + public static Board 자유게시판; + + @Autowired + private SiteUserRepository siteUserRepository; + @Autowired private RegionRepository regionRepository; @@ -72,16 +94,41 @@ public abstract class BaseIntegrationTest { @Autowired private LanguageRequirementRepository languageRequirementRepository; + @Autowired + private BoardRepository boardRepository; + @Value("${university.term}") public String term; @BeforeEach public void setUpBaseData() { + setUpSiteUsers(); setUpRegions(); setUpCountries(); setUpUniversities(); setUpUniversityInfos(); setUpLanguageRequirements(); + setUpBoards(); + } + + private void setUpSiteUsers() { + 테스트유저_1 = siteUserRepository.save(new SiteUser( + "test1@example.com", + "nickname1", + "profileImageUrl", + "1999-01-01", + PreparationStatus.CONSIDERING, + Role.MENTEE, + Gender.MALE)); + + 테스트유저_2 = siteUserRepository.save(new SiteUser( + "test2@example.com", + "nickname2", + "profileImageUrl", + "1999-01-01", + PreparationStatus.CONSIDERING, + Role.MENTEE, + Gender.FEMALE)); } private void setUpRegions() { @@ -283,6 +330,13 @@ private void setUpLanguageRequirements() { saveLanguageTestRequirement(메이지대학_지원_정보, LanguageTestType.JLPT, "N2"); } + private void setUpBoards() { + 미주권 = boardRepository.save(new Board(AMERICAS.name(), "미주권")); + 아시아권 = boardRepository.save(new Board(ASIA.name(), "아시아권")); + 유럽권 = boardRepository.save(new Board(EUROPE.name(), "유럽권")); + 자유게시판 = boardRepository.save(new Board(FREE.name(), "자유게시판")); + } + private void saveLanguageTestRequirement( UniversityInfoForApply universityInfoForApply, LanguageTestType testType, diff --git a/src/test/java/com/example/solidconnection/unit/service/PostServiceTest.java b/src/test/java/com/example/solidconnection/unit/service/PostServiceTest.java index 57c5916a9..afc899255 100644 --- a/src/test/java/com/example/solidconnection/unit/service/PostServiceTest.java +++ b/src/test/java/com/example/solidconnection/unit/service/PostServiceTest.java @@ -14,7 +14,9 @@ import com.example.solidconnection.post.domain.Post; import com.example.solidconnection.post.dto.*; import com.example.solidconnection.post.repository.PostRepository; -import com.example.solidconnection.post.service.PostService; +import com.example.solidconnection.post.service.PostCommandService; +import com.example.solidconnection.post.service.PostLikeService; +import com.example.solidconnection.post.service.PostQueryService; import com.example.solidconnection.s3.S3Service; import com.example.solidconnection.s3.UploadedFileUrlResponse; import com.example.solidconnection.service.RedisService; @@ -33,21 +35,41 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import java.util.*; - -import static com.example.solidconnection.custom.exception.ErrorCode.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; +import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_BOARD_CODE; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_CATEGORY; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ID; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_LIKE; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) @DisplayName("게시글 서비스 테스트") class PostServiceTest { + + @InjectMocks + PostQueryService postQueryService; + + @InjectMocks + PostCommandService postCommandService; + @InjectMocks - PostService postService; + PostLikeService postLikeService; + @Mock PostRepository postRepository; @Mock @@ -75,7 +97,6 @@ class PostServiceTest { private List imageFilesWithMoreThanFiveFiles; private List uploadedFileUrlResponseList; - @BeforeEach void setUp() { siteUser = createSiteUser(); @@ -206,7 +227,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(postRepository.save(any(Post.class))).thenReturn(postWithImages); // When - PostCreateResponse postCreateResponse = postService.createPost( + PostCreateResponse postCreateResponse = postCommandService.createPost( siteUser.getEmail(), board.getCode(), postCreateRequest, imageFiles); // Then @@ -227,7 +248,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(postRepository.save(postCreateRequest.toEntity(siteUser, board))).thenReturn(post); // When - PostCreateResponse postCreateResponse = postService.createPost( + PostCreateResponse postCreateResponse = postCommandService.createPost( siteUser.getEmail(), board.getCode(), postCreateRequest, Collections.emptyList()); // Then @@ -245,7 +266,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { "자유", "title", "content", false); // When & Then - CustomException exception = assertThrows(CustomException.class, () -> postService + CustomException exception = assertThrows(CustomException.class, () -> postCommandService .createPost(siteUser.getEmail(), invalidBoardCode, postCreateRequest, Collections.emptyList())); assertThat(exception.getMessage()) .isEqualTo(INVALID_BOARD_CODE.getMessage()); @@ -261,7 +282,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { invalidPostCategory, "title", "content", false); // When & Then - CustomException exception = assertThrows(CustomException.class, () -> postService + CustomException exception = assertThrows(CustomException.class, () -> postCommandService .createPost(siteUser.getEmail(), board.getCode(), postCreateRequest, Collections.emptyList())); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_CATEGORY.getMessage()); @@ -276,7 +297,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { "자유", "title", "content", false); // When & Then - CustomException exception = assertThrows(CustomException.class, () -> postService + CustomException exception = assertThrows(CustomException.class, () -> postCommandService .createPost(siteUser.getEmail(), board.getCode(), postCreateRequest, imageFilesWithMoreThanFiveFiles)); assertThat(exception.getMessage()) .isEqualTo(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES.getMessage()); @@ -294,7 +315,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(postRepository.getById(post.getId())).thenReturn(post); // When - PostUpdateResponse response = postService.updatePost( + PostUpdateResponse response = postCommandService.updatePost( siteUser.getEmail(), board.getCode(), post.getId(), postUpdateRequest, Collections.emptyList()); // Then @@ -311,7 +332,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(postRepository.getById(postWithImages.getId())).thenReturn(postWithImages); // When - PostUpdateResponse response = postService.updatePost( + PostUpdateResponse response = postCommandService.updatePost( siteUser.getEmail(), board.getCode(), postWithImages.getId(), postUpdateRequest, Collections.emptyList()); // Then @@ -329,7 +350,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(s3Service.uploadFiles(imageFiles, ImgType.COMMUNITY)).thenReturn(uploadedFileUrlResponseList); // When - PostUpdateResponse response = postService.updatePost( + PostUpdateResponse response = postCommandService.updatePost( siteUser.getEmail(), board.getCode(), post.getId(), postUpdateRequest, imageFiles); // Then @@ -347,7 +368,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(s3Service.uploadFiles(imageFiles, ImgType.COMMUNITY)).thenReturn(uploadedFileUrlResponseList); // When - PostUpdateResponse response = postService.updatePost( + PostUpdateResponse response = postCommandService.updatePost( siteUser.getEmail(), board.getCode(), postWithImages.getId(), postUpdateRequest, imageFiles); // Then @@ -365,11 +386,11 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.updatePost(siteUser.getEmail(), invalidBoardCode, post.getId(), postUpdateRequest, imageFiles)); + postCommandService.updatePost(siteUser.getEmail(), invalidBoardCode, post.getId(), postUpdateRequest, imageFiles)); assertThat(exception.getMessage()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getMessage()); + .isEqualTo(INVALID_BOARD_CODE.getMessage()); assertThat(exception.getCode()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getCode()); + .isEqualTo(INVALID_BOARD_CODE.getCode()); } @Test @@ -381,7 +402,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.updatePost(siteUser.getEmail(), board.getCode(), invalidPostId, postUpdateRequest, imageFiles)); + postCommandService.updatePost(siteUser.getEmail(), board.getCode(), invalidPostId, postUpdateRequest, imageFiles)); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ID.getMessage()); assertThat(exception.getCode()) @@ -397,7 +418,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.updatePost(invalidEmail, board.getCode(), post.getId(), postUpdateRequest, imageFiles)); + postCommandService.updatePost(invalidEmail, board.getCode(), post.getId(), postUpdateRequest, imageFiles)); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ACCESS.getMessage()); assertThat(exception.getCode()) @@ -412,14 +433,13 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.updatePost(siteUser.getEmail(), board.getCode(), questionPost.getId(), postUpdateRequest, imageFiles)); + postCommandService.updatePost(siteUser.getEmail(), board.getCode(), questionPost.getId(), postUpdateRequest, imageFiles)); assertThat(exception.getMessage()) .isEqualTo(CAN_NOT_DELETE_OR_UPDATE_QUESTION.getMessage()); assertThat(exception.getCode()) .isEqualTo(CAN_NOT_DELETE_OR_UPDATE_QUESTION.getCode()); } - @Test void 게시글을_수정할_때_파일_수가_5개를_넘는다면_예외_응답을_반환한다() { // Given @@ -428,7 +448,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.updatePost(siteUser.getEmail(), board.getCode(), post.getId(), postUpdateRequest, imageFilesWithMoreThanFiveFiles)); + postCommandService.updatePost(siteUser.getEmail(), board.getCode(), post.getId(), postUpdateRequest, imageFilesWithMoreThanFiveFiles)); assertThat(exception.getMessage()) .isEqualTo(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES.getMessage()); assertThat(exception.getCode()) @@ -448,7 +468,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(commentService.findCommentsByPostId(siteUser.getEmail(), post.getId())).thenReturn(commentFindResultDTOList); // When - PostFindResponse response = postService.findPostById(siteUser.getEmail(), board.getCode(), post.getId()); + PostFindResponse response = postQueryService.findPostById(siteUser.getEmail(), board.getCode(), post.getId()); // Then PostFindResponse expectedResponse = PostFindResponse.from( @@ -474,11 +494,11 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.findPostById(siteUser.getEmail(), invalidBoardCode, post.getId())); + postQueryService.findPostById(siteUser.getEmail(), invalidBoardCode, post.getId())); assertThat(exception.getMessage()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getMessage()); + .isEqualTo(INVALID_BOARD_CODE.getMessage()); assertThat(exception.getCode()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getCode()); + .isEqualTo(INVALID_BOARD_CODE.getCode()); } @Test @@ -489,7 +509,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.findPostById(siteUser.getEmail(), board.getCode(), invalidPostId)); + postQueryService.findPostById(siteUser.getEmail(), board.getCode(), invalidPostId)); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ID.getMessage()); assertThat(exception.getCode()) @@ -505,7 +525,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(postRepository.getById(post.getId())).thenReturn(post); // When - PostDeleteResponse postDeleteResponse = postService.deletePostById(siteUser.getEmail(), board.getCode(), post.getId()); + PostDeleteResponse postDeleteResponse = postCommandService.deletePostById(siteUser.getEmail(), board.getCode(), post.getId()); // Then assertEquals(postDeleteResponse.id(), post.getId()); @@ -521,11 +541,11 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.deletePostById(siteUser.getEmail(), invalidBoardCode, post.getId())); + postCommandService.deletePostById(siteUser.getEmail(), invalidBoardCode, post.getId())); assertThat(exception.getMessage()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getMessage()); + .isEqualTo(INVALID_BOARD_CODE.getMessage()); assertThat(exception.getCode()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getCode()); + .isEqualTo(INVALID_BOARD_CODE.getCode()); } @Test @@ -536,11 +556,11 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.deletePostById(siteUser.getEmail(), board.getCode(), invalidPostId)); + postCommandService.deletePostById(siteUser.getEmail(), board.getCode(), invalidPostId)); assertThat(exception.getMessage()) - .isEqualTo(ErrorCode.INVALID_POST_ID.getMessage()); + .isEqualTo(INVALID_POST_ID.getMessage()); assertThat(exception.getCode()) - .isEqualTo(ErrorCode.INVALID_POST_ID.getCode()); + .isEqualTo(INVALID_POST_ID.getCode()); } @Test @@ -551,7 +571,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.deletePostById(invalidEmail, board.getCode(), post.getId()) + postCommandService.deletePostById(invalidEmail, board.getCode(), post.getId()) ); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ACCESS.getMessage()); @@ -565,11 +585,11 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.deletePostById(siteUser.getEmail(), board.getCode(), questionPost.getId())); + postCommandService.deletePostById(siteUser.getEmail(), board.getCode(), questionPost.getId())); assertThat(exception.getMessage()) - .isEqualTo(ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION.getMessage()); + .isEqualTo(CAN_NOT_DELETE_OR_UPDATE_QUESTION.getMessage()); assertThat(exception.getCode()) - .isEqualTo(ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION.getCode()); + .isEqualTo(CAN_NOT_DELETE_OR_UPDATE_QUESTION.getCode()); } /** @@ -582,7 +602,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); // When - PostLikeResponse postLikeResponse = postService.likePost(siteUser.getEmail(), board.getCode(), post.getId()); + PostLikeResponse postLikeResponse = postLikeService.likePost(siteUser.getEmail(), board.getCode(), post.getId()); // Then assertEquals(postLikeResponse, PostLikeResponse.from(post)); @@ -597,7 +617,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.likePost(siteUser.getEmail(), board.getCode(), post.getId())); + postLikeService.likePost(siteUser.getEmail(), board.getCode(), post.getId())); assertThat(exception.getMessage()) .isEqualTo(ErrorCode.DUPLICATE_POST_LIKE.getMessage()); assertThat(exception.getCode()) @@ -611,11 +631,11 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.likePost(siteUser.getEmail(), invalidBoardCode, post.getId())); + postLikeService.likePost(siteUser.getEmail(), invalidBoardCode, post.getId())); assertThat(exception.getMessage()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getMessage()); + .isEqualTo(INVALID_BOARD_CODE.getMessage()); assertThat(exception.getCode()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getCode()); + .isEqualTo(INVALID_BOARD_CODE.getCode()); } @Test @@ -626,7 +646,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.likePost(siteUser.getEmail(), board.getCode(), invalidPostId)); + postLikeService.likePost(siteUser.getEmail(), board.getCode(), invalidPostId)); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ID.getMessage()); assertThat(exception.getCode()) @@ -642,7 +662,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { when(postLikeRepository.getByPostAndSiteUser(post, siteUser)).thenReturn(postLike); // When - PostDislikeResponse postDislikeResponse = postService.dislikePost(siteUser.getEmail(), board.getCode(), post.getId()); + PostDislikeResponse postDislikeResponse = postLikeService.dislikePost(siteUser.getEmail(), board.getCode(), post.getId()); // Then assertEquals(postDislikeResponse, PostDislikeResponse.from(post)); @@ -657,11 +677,11 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.dislikePost(siteUser.getEmail(), board.getCode(), post.getId())); + postLikeService.dislikePost(siteUser.getEmail(), board.getCode(), post.getId())); assertThat(exception.getMessage()) - .isEqualTo(ErrorCode.INVALID_POST_LIKE.getMessage()); + .isEqualTo(INVALID_POST_LIKE.getMessage()); assertThat(exception.getCode()) - .isEqualTo(ErrorCode.INVALID_POST_LIKE.getCode()); + .isEqualTo(INVALID_POST_LIKE.getCode()); } @Test @@ -671,11 +691,11 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.dislikePost(siteUser.getEmail(), invalidBoardCode, post.getId())); + postLikeService.dislikePost(siteUser.getEmail(), invalidBoardCode, post.getId())); assertThat(exception.getMessage()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getMessage()); + .isEqualTo(INVALID_BOARD_CODE.getMessage()); assertThat(exception.getCode()) - .isEqualTo(ErrorCode.INVALID_BOARD_CODE.getCode()); + .isEqualTo(INVALID_BOARD_CODE.getCode()); } @Test @@ -686,7 +706,7 @@ private List createMockImageFilesWithMoreThanFiveFiles() { // When & Then CustomException exception = assertThrows(CustomException.class, () -> - postService.dislikePost(siteUser.getEmail(), board.getCode(), invalidPostId)); + postLikeService.dislikePost(siteUser.getEmail(), board.getCode(), invalidPostId)); assertThat(exception.getMessage()) .isEqualTo(INVALID_POST_ID.getMessage()); assertThat(exception.getCode())