From 7ef315f2d3cc870fdc4ccd1974201bf3ec5e54b3 Mon Sep 17 00:00:00 2001 From: Jun-Hyeok Sin Date: Thu, 3 Aug 2023 11:19:42 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#186)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: (#174) 게시글 상세 조회 기능 Dto 구현 * feat: (#174) 게시글 상세 조회 기능 구현 (아직 Post content의 imageUrl 필드는 추가 못함) * refactor: (#174) response dto에 Post content의 imageUrl 필드 추가 * refactor: (#174) 게시글 내용의 이미지 데이터 처리 로직 추가 * refactor: (#174) dto 관련 클래스들을 패키지로 나누기 * refactor: (#174) 이미 tostring이 있는 record에서 tostring 삭제 * refactor: (#174) stream() 코드를 더 가독성이 높게 하나의 메서드마다 개행을 해주는 것으로 변경 * refactor: (#174) 게시글 작성자가 아닐 시, 상세조회 예외 처리 기능 구현 * refactor: (#174) 로그인 한 회원의 변수명 통일 * refactor: (#174) contentImages가 있는지 확인하는 메서드 명 개선 * refactor: (#174) null 체크 메서드를 isNull에서 nonNull로 개선 * refactor: (#174) 페이징 제외한 원시 타입을 래퍼 클래스로 원복 * refactor: (#174) 출력문 제서 --- .../post/controller/PostController.java | 29 +++- .../post/dto/response/CategoryResponse.java | 9 +- .../post/dto/response/PostOptionResponse.java | 16 +-- .../post/dto/response/PostResponse.java | 23 +--- .../post/dto/response/WriterResponse.java | 8 -- .../response/detail/PostDetailResponse.java | 76 ++++++++++ .../detail/PostOptionDetailResponse.java | 27 ++++ .../response/detail/VoteDetailResponse.java | 19 +++ .../VoteCountForAgeGroupResponse.java | 2 +- .../VoteOptionStatisticsResponse.java | 2 +- .../dto/response/{ => vote}/VoteResponse.java | 3 +- .../domain/post/service/PostService.java | 35 ++++- .../post/controller/PostControllerTest.java | 58 +++++++- .../domain/post/service/PostServiceTest.java | 130 +++++++++++++++++- 14 files changed, 372 insertions(+), 65 deletions(-) create mode 100644 backend/src/main/java/com/votogether/domain/post/dto/response/detail/PostDetailResponse.java create mode 100644 backend/src/main/java/com/votogether/domain/post/dto/response/detail/PostOptionDetailResponse.java create mode 100644 backend/src/main/java/com/votogether/domain/post/dto/response/detail/VoteDetailResponse.java rename backend/src/main/java/com/votogether/domain/post/dto/response/{ => vote}/VoteCountForAgeGroupResponse.java (92%) rename backend/src/main/java/com/votogether/domain/post/dto/response/{ => vote}/VoteOptionStatisticsResponse.java (97%) rename backend/src/main/java/com/votogether/domain/post/dto/response/{ => vote}/VoteResponse.java (85%) diff --git a/backend/src/main/java/com/votogether/domain/post/controller/PostController.java b/backend/src/main/java/com/votogether/domain/post/controller/PostController.java index 04dd15bb5..5f437ba32 100644 --- a/backend/src/main/java/com/votogether/domain/post/controller/PostController.java +++ b/backend/src/main/java/com/votogether/domain/post/controller/PostController.java @@ -3,7 +3,8 @@ import com.votogether.domain.member.entity.Member; import com.votogether.domain.post.dto.request.PostCreateRequest; import com.votogether.domain.post.dto.response.PostResponse; -import com.votogether.domain.post.dto.response.VoteOptionStatisticsResponse; +import com.votogether.domain.post.dto.response.detail.PostDetailResponse; +import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse; import com.votogether.domain.post.entity.PostClosingType; import com.votogether.domain.post.entity.PostSortType; import com.votogether.domain.post.service.PostService; @@ -37,7 +38,7 @@ public class PostController { @Operation(summary = "게시글 작성", description = "게시글을 저장한다.") @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "게시물 생성되었습니다."), + @ApiResponse(responseCode = "201", description = "게시글 생성 성공"), @ApiResponse(responseCode = "400", description = "잘못된 입력입니다.") }) @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @@ -45,7 +46,7 @@ public ResponseEntity save( @RequestPart @Valid final PostCreateRequest request, @RequestPart(required = false) final List contentImages, @RequestPart final List optionImages, - @Auth final Member loginMember + @Auth final Member member ) { System.out.println("PostController.save"); @@ -59,13 +60,13 @@ public ResponseEntity save( System.out.println("optionImages1 = " + optionImages.get(0).getOriginalFilename()); System.out.println("optionImages2 = " + optionImages.get(1).getOriginalFilename()); } - final long postId = postService.save(request, loginMember, contentImages, optionImages); + final long postId = postService.save(request, member, contentImages, optionImages); return ResponseEntity.created(URI.create("/posts/" + postId)).build(); } @Operation(summary = "전체 게시글 조회", description = "게시글을 조회한다.") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "게시글을 조회했습니다."), + @ApiResponse(responseCode = "200", description = "게시글 조회 성공"), @ApiResponse(responseCode = "400", description = "잘못된 입력입니다.") }) @GetMapping @@ -73,14 +74,28 @@ public ResponseEntity> getAllPost( final int page, final PostClosingType postClosingType, final PostSortType postSortType, - @Auth final Member loginMember + @Auth final Member member ) { final List responses = - postService.getAllPostBySortTypeAndClosingType(loginMember, page, postClosingType, postSortType); + postService.getAllPostBySortTypeAndClosingType(member, page, postClosingType, postSortType); return ResponseEntity.ok(responses); } + @Operation(summary = "게시글 상세 조회", description = "한 게시글의 상세를 조회한다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "게시글 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 게시글") + }) + @GetMapping("{postId}") + public ResponseEntity getPost( + @PathVariable final Long postId, + @Auth final Member member + ) { + final PostDetailResponse response = postService.getPostById(postId, member); + return ResponseEntity.ok(response); + } + @Operation(summary = "게시글 투표 통계 조회", description = "게시글 투표에 대한 전체 통계를 조회한다.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "게시글 투표 통계 조회 성공"), diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/CategoryResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/CategoryResponse.java index ba3207659..655dac7c8 100644 --- a/backend/src/main/java/com/votogether/domain/post/dto/response/CategoryResponse.java +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/CategoryResponse.java @@ -3,7 +3,7 @@ import com.votogether.domain.category.entity.Category; public record CategoryResponse( - long id, + Long id, String name ) { @@ -11,11 +11,4 @@ public static CategoryResponse of(Category category) { return new CategoryResponse(category.getId(), category.getName()); } - @Override - public String toString() { - return "CategoryResponse{" + - "id=" + id + - ", name='" + name + '\'' + - '}'; - } } diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/PostOptionResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/PostOptionResponse.java index 20ced6840..1d2bfd19d 100644 --- a/backend/src/main/java/com/votogether/domain/post/dto/response/PostOptionResponse.java +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/PostOptionResponse.java @@ -3,10 +3,10 @@ import com.votogether.domain.post.entity.PostOption; public record PostOptionResponse( - long optionId, + Long optionId, String content, - int voteCount, - double votePercent + Integer voteCount, + Double votePercent ) { public static PostOptionResponse of( @@ -22,14 +22,4 @@ public static PostOptionResponse of( ); } - @Override - public String toString() { - return "OptionResponse{" + - "optionId=" + optionId + - ", content='" + content + '\'' + - ", voteCount=" + voteCount + - ", votePercent=" + votePercent + - '}'; - } - } diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/PostResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/PostResponse.java index 4dbf55101..58d506164 100644 --- a/backend/src/main/java/com/votogether/domain/post/dto/response/PostResponse.java +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/PostResponse.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.dto.response.vote.VoteResponse; import com.votogether.domain.post.entity.Post; import com.votogether.domain.post.entity.PostBody; import com.votogether.domain.post.entity.PostCategory; @@ -47,7 +48,9 @@ public static PostResponse of(final Post post, final Member loginMember) { } private static List getCategories(final Post post) { - return post.getPostCategories().getPostCategories().stream() + return post.getPostCategories() + .getPostCategories() + .stream() .map(PostCategory::getCategory) .map(CategoryResponse::of) .toList(); @@ -57,7 +60,9 @@ private static List getOptions( final Post post, final Member loginMember ) { - return post.getPostOptions().getPostOptions().stream() + return post.getPostOptions() + .getPostOptions() + .stream() .map(postOption -> PostOptionResponse.of( postOption, @@ -68,18 +73,4 @@ private static List getOptions( .toList(); } - @Override - public String toString() { - return "PostResponse{" + - "postId=" + postId + - ", writer=" + writer + - ", title='" + title + '\'' + - ", content='" + content + '\'' + - ", categories=" + categories + - ", createdAt=" + createdAt + - ", deadline=" + deadline + - ", voteInfo=" + voteInfo + - '}' + "\n\n"; - } - } diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/WriterResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/WriterResponse.java index 5010c11ce..b53657d64 100644 --- a/backend/src/main/java/com/votogether/domain/post/dto/response/WriterResponse.java +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/WriterResponse.java @@ -9,12 +9,4 @@ public static WriterResponse of(final Long id, final String nickname) { return new WriterResponse(id, nickname); } - @Override - public String toString() { - return "WriterResponse{" + - "id=" + id + - ", nickname='" + nickname + '\'' + - '}'; - } - } diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/detail/PostDetailResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/detail/PostDetailResponse.java new file mode 100644 index 000000000..8d607fb18 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/detail/PostDetailResponse.java @@ -0,0 +1,76 @@ +package com.votogether.domain.post.dto.response.detail; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.post.dto.response.CategoryResponse; +import com.votogether.domain.post.dto.response.WriterResponse; +import com.votogether.domain.post.entity.Post; +import com.votogether.domain.post.entity.PostBody; +import com.votogether.domain.post.entity.PostCategories; +import com.votogether.domain.post.entity.PostCategory; +import com.votogether.domain.post.entity.PostContentImage; +import java.time.LocalDateTime; +import java.util.List; + +public record PostDetailResponse( + Long postId, + WriterResponse writer, + String title, + String content, + String imageUrl, + List categories, + LocalDateTime createdAt, + LocalDateTime deadline, + VoteDetailResponse voteInfo +) { + + public static PostDetailResponse of(final Post post, final Member loginMember) { + final Member writer = post.getWriter(); + final PostBody postBody = post.getPostBody(); + final List contentImages = postBody.getPostContentImages().getContentImages(); + final StringBuilder contentImageUrl = new StringBuilder(); + + if (!contentImages.isEmpty()) { + contentImageUrl.append(contentImages.get(0).getImageUrl()); + } + + final PostCategories postCategories = post.getPostCategories(); + return new PostDetailResponse( + post.getId(), + WriterResponse.of(writer.getId(), writer.getNickname()), + postBody.getTitle(), + postBody.getContent(), + contentImageUrl.toString(), + getCategories(postCategories.getPostCategories()), + post.getCreatedAt(), + post.getDeadline(), + VoteDetailResponse.of( + post.getPostOptions().getSelectedOptionId(loginMember), + post.getFinalTotalVoteCount(loginMember), + getOptions(post, loginMember) + ) + ); + } + + private static List getCategories(final List postCategories) { + return postCategories.stream() + .map(PostCategory::getCategory) + .map(CategoryResponse::of) + .toList(); + } + + private static List getOptions( + final Post post, + final Member loginMember + ) { + return post.getPostOptions().getPostOptions().stream() + .map(postOption -> + PostOptionDetailResponse.of( + postOption, + post.isVisibleVoteResult(loginMember), + post.getFinalTotalVoteCount(loginMember) + ) + ) + .toList(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/detail/PostOptionDetailResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/detail/PostOptionDetailResponse.java new file mode 100644 index 000000000..01adac12c --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/detail/PostOptionDetailResponse.java @@ -0,0 +1,27 @@ +package com.votogether.domain.post.dto.response.detail; + +import com.votogether.domain.post.entity.PostOption; + +public record PostOptionDetailResponse( + Long optionId, + String content, + String imageUrl, + Integer voteCount, + Double votePercent +) { + + public static PostOptionDetailResponse of( + final PostOption postOption, + final Boolean isVisibleVoteResult, + final Long totalVoteCount + ) { + return new PostOptionDetailResponse( + postOption.getId(), + postOption.getContent(), + postOption.getImageUrl(), + postOption.getVoteCount(isVisibleVoteResult), + postOption.getVotePercent(totalVoteCount) + ); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/detail/VoteDetailResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/detail/VoteDetailResponse.java new file mode 100644 index 000000000..aba215c76 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/detail/VoteDetailResponse.java @@ -0,0 +1,19 @@ +package com.votogether.domain.post.dto.response.detail; + +import java.util.List; + +public record VoteDetailResponse( + Long selectedOptionId, + Long totalVoteCount, + List options +) { + + public static VoteDetailResponse of( + final long selectedOptionId, + final long finalTotalVoteCount, + final List options + ) { + return new VoteDetailResponse(selectedOptionId, finalTotalVoteCount, options); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/VoteCountForAgeGroupResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteCountForAgeGroupResponse.java similarity index 92% rename from backend/src/main/java/com/votogether/domain/post/dto/response/VoteCountForAgeGroupResponse.java rename to backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteCountForAgeGroupResponse.java index cf6e2a625..48f180107 100644 --- a/backend/src/main/java/com/votogether/domain/post/dto/response/VoteCountForAgeGroupResponse.java +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteCountForAgeGroupResponse.java @@ -1,4 +1,4 @@ -package com.votogether.domain.post.dto.response; +package com.votogether.domain.post.dto.response.vote; import com.votogether.domain.member.entity.Gender; import java.util.Map; diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/VoteOptionStatisticsResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteOptionStatisticsResponse.java similarity index 97% rename from backend/src/main/java/com/votogether/domain/post/dto/response/VoteOptionStatisticsResponse.java rename to backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteOptionStatisticsResponse.java index 433f51b39..842437a0e 100644 --- a/backend/src/main/java/com/votogether/domain/post/dto/response/VoteOptionStatisticsResponse.java +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteOptionStatisticsResponse.java @@ -1,4 +1,4 @@ -package com.votogether.domain.post.dto.response; +package com.votogether.domain.post.dto.response.vote; import com.votogether.domain.member.entity.Gender; import java.util.Arrays; diff --git a/backend/src/main/java/com/votogether/domain/post/dto/response/VoteResponse.java b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteResponse.java similarity index 85% rename from backend/src/main/java/com/votogether/domain/post/dto/response/VoteResponse.java rename to backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteResponse.java index 0c2c55480..e238fdb42 100644 --- a/backend/src/main/java/com/votogether/domain/post/dto/response/VoteResponse.java +++ b/backend/src/main/java/com/votogether/domain/post/dto/response/vote/VoteResponse.java @@ -1,5 +1,6 @@ -package com.votogether.domain.post.dto.response; +package com.votogether.domain.post.dto.response.vote; +import com.votogether.domain.post.dto.response.PostOptionResponse; import java.util.List; public record VoteResponse( diff --git a/backend/src/main/java/com/votogether/domain/post/service/PostService.java b/backend/src/main/java/com/votogether/domain/post/service/PostService.java index 6293b530c..d3ceff581 100644 --- a/backend/src/main/java/com/votogether/domain/post/service/PostService.java +++ b/backend/src/main/java/com/votogether/domain/post/service/PostService.java @@ -5,9 +5,10 @@ import com.votogether.domain.member.entity.Gender; import com.votogether.domain.member.entity.Member; import com.votogether.domain.post.dto.request.PostCreateRequest; +import com.votogether.domain.post.dto.response.detail.PostDetailResponse; import com.votogether.domain.post.dto.request.PostOptionCreateRequest; import com.votogether.domain.post.dto.response.PostResponse; -import com.votogether.domain.post.dto.response.VoteOptionStatisticsResponse; +import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse; import com.votogether.domain.post.entity.Post; import com.votogether.domain.post.entity.PostBody; import com.votogether.domain.post.entity.PostClosingType; @@ -26,6 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.data.domain.PageRequest; @@ -93,9 +95,12 @@ private Post toPostEntity( ) { final Post post = toPost(postCreateRequest, loginMember); - post.mapPostOptionsByElements(getPostOptionContents(postCreateRequest), parseOptionImageUrls(optionImages)); + post.mapPostOptionsByElements( + getPostOptionContents(postCreateRequest), + uploadAndParseOptionImageUrls(optionImages) + ); post.mapCategories(categories); - post.addContentImage(ImageUploader.upload(contentImages.get(0))); + addContentImageIfPresent(post, contentImages); return post; } @@ -124,12 +129,22 @@ private List getPostOptionContents(final PostCreateRequest postCreateReq .toList(); } - private List parseOptionImageUrls(final List optionImages) { + private List uploadAndParseOptionImageUrls(final List optionImages) { return optionImages.stream() .map(ImageUploader::upload) .toList(); } + private void addContentImageIfPresent(final Post post, final List contentImages) { + if (isContentImagesPresent(contentImages)) { + post.addContentImage(ImageUploader.upload(contentImages.get(0))); + } + } + + private boolean isContentImagesPresent(final List contentImages) { + return Objects.nonNull(contentImages) && !contentImages.isEmpty(); + } + @Transactional(readOnly = true) public List getAllPostBySortTypeAndClosingType( final Member loginMember, @@ -154,6 +169,16 @@ private List findContentsBySortTypeAndClosingType( .getContent(); } + @Transactional(readOnly = true) + public PostDetailResponse getPostById(final Long postId, final Member loginMember) { + final Post post = postRepository.findById(postId) + .orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); + + post.validateWriter(loginMember); + + return PostDetailResponse.of(post, loginMember); + } + @Transactional(readOnly = true) public VoteOptionStatisticsResponse getVoteStatistics(final long postId, final Member member) { final Post post = postRepository.findById(postId) @@ -213,7 +238,7 @@ private String groupAgeRange(final String ageRange) { return ageRange; } - @Transactional + @Transactional(readOnly = true) public void closePostEarlyById(final Long id, final Member loginMember) { final Post post = postRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("해당 게시글은 존재하지 않습니다.")); diff --git a/backend/src/test/java/com/votogether/domain/post/controller/PostControllerTest.java b/backend/src/test/java/com/votogether/domain/post/controller/PostControllerTest.java index d7e4ce56d..fcd1b4e49 100644 --- a/backend/src/test/java/com/votogether/domain/post/controller/PostControllerTest.java +++ b/backend/src/test/java/com/votogether/domain/post/controller/PostControllerTest.java @@ -17,15 +17,19 @@ import com.votogether.domain.member.service.MemberService; import com.votogether.domain.post.dto.request.PostCreateRequest; import com.votogether.domain.post.dto.request.PostOptionCreateRequest; +import com.votogether.domain.post.dto.response.detail.PostDetailResponse; import com.votogether.domain.post.dto.response.PostResponse; -import com.votogether.domain.post.dto.response.VoteCountForAgeGroupResponse; -import com.votogether.domain.post.dto.response.VoteOptionStatisticsResponse; +import com.votogether.domain.post.dto.response.vote.VoteCountForAgeGroupResponse; +import com.votogether.domain.post.dto.response.detail.VoteDetailResponse; +import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse; +import com.votogether.domain.post.dto.response.WriterResponse; import com.votogether.domain.post.entity.Post; import com.votogether.domain.post.entity.PostBody; import com.votogether.domain.post.entity.PostClosingType; import com.votogether.domain.post.entity.PostSortType; import com.votogether.domain.post.service.PostService; import com.votogether.exception.GlobalExceptionHandler; +import com.votogether.fixtures.MemberFixtures; import com.votogether.global.jwt.TokenProcessor; import io.restassured.http.ContentType; import io.restassured.module.mockmvc.RestAssuredMockMvc; @@ -186,7 +190,7 @@ void throwExceptionBlankTitle() throws IOException { } @Test - @DisplayName("정렬 유형 및 마감 유형별로 모든 게시물 가져온다") + @DisplayName("정렬 유형 및 마감 유형별로 모든 게시물 조회한다") void getAllPostBySortTypeAndClosingType() throws JsonProcessingException { // given int firstPage = 0; @@ -222,7 +226,6 @@ void getAllPostBySortTypeAndClosingType() throws JsonProcessingException { List responses = mapper.readValue(responseBody, new TypeReference<>() { }); - System.out.println(responses.get(0).deadline()); // then assertAll( @@ -231,6 +234,53 @@ void getAllPostBySortTypeAndClosingType() throws JsonProcessingException { ); } + @Test + @DisplayName("한 게시글의 상세를 조회한다.") + void getPost() throws JsonProcessingException { + // given + long postId = 0L; + Member writer = MALE_30.get(); + LocalDateTime deadline = LocalDateTime.now().plusDays(3L); + + PostBody postBody = PostBody.builder() + .title("title") + .content("content") + .build(); + + Post post = Post.builder() + .writer(writer) + .postBody(postBody) + .deadline(deadline) + .build(); + + Member member = MemberFixtures.MALE_20.get(); + given(postService.getPostById(postId, member)).willReturn(PostDetailResponse.of(post, member)); + + // when + String responseBody = RestAssuredMockMvc.given().log().all() + .when().get("/posts/{postId}", postId) + .then().log().all() + .contentType(ContentType.JSON) + .status(HttpStatus.OK) + .extract().asString(); + + PostDetailResponse response = mapper.readValue(responseBody, new TypeReference<>() { + }); + + // then + WriterResponse writerResponse = response.writer(); + VoteDetailResponse voteDetailResponse = response.voteInfo(); + + assertAll( + () -> assertThat(response.title()).isEqualTo("title"), + () -> assertThat(response.content()).isEqualTo("content"), + () -> assertThat(response.deadline()).isEqualTo(deadline), + () -> assertThat(writerResponse.id()).isEqualTo(member.getId()), + () -> assertThat(writerResponse.nickname()).isEqualTo("user9"), + () -> assertThat(voteDetailResponse.totalVoteCount()).isZero() + ); + } + @Test @DisplayName("게시글에 대한 전체 투표 통계를 조회한다.") void getVoteStatistics() { diff --git a/backend/src/test/java/com/votogether/domain/post/service/PostServiceTest.java b/backend/src/test/java/com/votogether/domain/post/service/PostServiceTest.java index 33b14a3af..751b8d26d 100644 --- a/backend/src/test/java/com/votogether/domain/post/service/PostServiceTest.java +++ b/backend/src/test/java/com/votogether/domain/post/service/PostServiceTest.java @@ -21,13 +21,19 @@ import com.votogether.domain.member.repository.MemberRepository; import com.votogether.domain.post.dto.request.PostCreateRequest; import com.votogether.domain.post.dto.request.PostOptionCreateRequest; +import com.votogether.domain.post.dto.response.CategoryResponse; import com.votogether.domain.post.dto.response.PostResponse; -import com.votogether.domain.post.dto.response.VoteOptionStatisticsResponse; +import com.votogether.domain.post.dto.response.WriterResponse; +import com.votogether.domain.post.dto.response.detail.PostDetailResponse; +import com.votogether.domain.post.dto.response.detail.PostOptionDetailResponse; +import com.votogether.domain.post.dto.response.detail.VoteDetailResponse; +import com.votogether.domain.post.dto.response.vote.VoteOptionStatisticsResponse; import com.votogether.domain.post.entity.Post; import com.votogether.domain.post.entity.PostBody; import com.votogether.domain.post.entity.PostClosingType; import com.votogether.domain.post.entity.PostOption; import com.votogether.domain.post.entity.PostSortType; +import com.votogether.domain.post.exception.PostExceptionType; import com.votogether.domain.post.repository.PostOptionRepository; import com.votogether.domain.post.repository.PostRepository; import com.votogether.domain.vote.entity.Vote; @@ -601,4 +607,126 @@ void getAllPostBySortTypeAndClosingType() { ); } + @Test + @DisplayName("한 게시글의 상세를 조회한다.") + void getPost() throws IOException { + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile( + "image1", + "test1.png", + "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG") + ); + MockMultipartFile file2 = new MockMultipartFile( + "image1", + "test2.png", + "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG") + ); + + LocalDateTime deadline = LocalDateTime.now().plusDays(3); + + PostOptionCreateRequest option1 = PostOptionCreateRequest.builder() + .content("option1") + .build(); + + PostOptionCreateRequest option2 = PostOptionCreateRequest.builder() + .content("option2") + .build(); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(category1.getId(), category2.getId())) + .title("title") + .content("content") + .postOptions(List.of(option1, option2)) + .deadline(deadline) + .build(); + + Long savedPostId = postService.save(postCreateRequest, member, List.of(), List.of(file1, file2)); + + // when + PostDetailResponse response = postService.getPostById(savedPostId, member); + + // then + List categories = response.categories(); + WriterResponse writer = response.writer(); + VoteDetailResponse voteDetailResponse = response.voteInfo(); + List options = voteDetailResponse.options(); + + assertAll( + () -> assertThat(response.postId()).isEqualTo(savedPostId), + () -> assertThat(response.title()).isEqualTo("title"), + () -> assertThat(response.content()).isEqualTo("content"), + () -> assertThat(response.deadline()).isEqualTo(deadline), + () -> assertThat(categories).hasSize(2), + () -> assertThat(categories.get(0).name()).isEqualTo("개발"), + () -> assertThat(writer.id()).isEqualTo(member.getId()), + () -> assertThat(writer.nickname()).isEqualTo("user7"), + () -> assertThat(voteDetailResponse.totalVoteCount()).isZero(), + () -> assertThat(options).hasSize(2), + () -> assertThat(options.get(0).imageUrl()).contains("test1.png") + ); + } + + @Test + @DisplayName("한 게시글의 상세를 조회할 때, 작성자가 아니면 예외를 던진다.") + void throwExceptionNotWriterGetPost() throws IOException { + Category category1 = categoryRepository.save(CategoryFixtures.DEVELOP.get()); + Category category2 = categoryRepository.save(CategoryFixtures.FOOD.get()); + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + MockMultipartFile file1 = new MockMultipartFile( + "image1", + "test1.png", + "image/png", + new FileInputStream("src/test/resources/images/testImage1.PNG") + ); + MockMultipartFile file2 = new MockMultipartFile( + "image1", + "test2.png", + "image/png", + new FileInputStream("src/test/resources/images/testImage2.PNG") + ); + + LocalDateTime deadline = LocalDateTime.now().plusDays(3); + + PostOptionCreateRequest option1 = PostOptionCreateRequest.builder() + .content("option1") + .build(); + + PostOptionCreateRequest option2 = PostOptionCreateRequest.builder() + .content("option2") + .build(); + + PostCreateRequest postCreateRequest = PostCreateRequest.builder() + .categoryIds(List.of(category1.getId(), category2.getId())) + .title("title") + .content("content") + .postOptions(List.of(option1, option2)) + .deadline(deadline) + .build(); + + Long savedPostId = postService.save(postCreateRequest, member, List.of(), List.of(file1, file2)); + + // when, then + assertThatThrownBy(() -> postService.getPostById(savedPostId, MemberFixtures.MALE_20.get())) + .isInstanceOf(BadRequestException.class) + .hasMessage(PostExceptionType.NOT_WRITER.getMessage()); + } + + @Test + @DisplayName("존재하지 않은 게시글을 가져오려 할 시, 예외를 던진다.") + void throwExceptionNotFoundPost() { + // given + Member member = memberRepository.save(MemberFixtures.MALE_20.get()); + + // when, then + assertThatThrownBy(() -> postService.getPostById(1L, member)) + .isInstanceOf(NotFoundException.class) + .hasMessage(PostExceptionType.POST_NOT_FOUND.getMessage()); + } + }