diff --git a/src/docs/asciidoc/api/pick/v2/pick-detail-v2.adoc b/src/docs/asciidoc/api/pick/v2/pick-detail-v2.adoc new file mode 100644 index 00000000..b6a68da6 --- /dev/null +++ b/src/docs/asciidoc/api/pick/v2/pick-detail-v2.adoc @@ -0,0 +1,34 @@ +[[Pick-Detail-V2]] +== 픽픽픽 상세 API V2(GET: /devdevdev/api/v2/picks/{pickId}) + +* 픽픽픽 아이디로 상세 화면을 조회할 수 있다. +* 픽픽픽 게시글의 상태가 승인 상태(`APPROVAL`)가 아니면 조회할 수 없다. +* V1과 비교하여 응답에 **투표수(voteTotalCount)**와 **댓글수(commentTotalCount)** 필드가 추가되었다. + +=== 정상 요청/응답 + +==== HTTP Request + +include::{snippets}/pick-detail-v2/http-request.adoc[] + +==== HTTP Request Header Fields + +include::{snippets}/pick-detail-v2/request-headers.adoc[] + +==== HTTP Request Path Parameters Fields + +include::{snippets}/pick-detail-v2/path-parameters.adoc[] + +==== HTTP Response + +include::{snippets}/pick-detail-v2/http-response.adoc[] + +==== HTTP Response Fields + +include::{snippets}/pick-detail-v2/response-fields.adoc[] + +=== V1과의 차이점 + +* `data.voteTotalCount`: 픽픽픽 전체 투표 수 (NEW) +* `data.commentTotalCount`: 픽픽픽 전체 댓글 수 (NEW) + diff --git a/src/docs/asciidoc/api/pick/v2/pick.adoc b/src/docs/asciidoc/api/pick/v2/pick.adoc index a8925e6a..cbacae15 100644 --- a/src/docs/asciidoc/api/pick/v2/pick.adoc +++ b/src/docs/asciidoc/api/pick/v2/pick.adoc @@ -1,5 +1,6 @@ = 픽픽픽 V2 include::pick-main-v2.adoc[] +include::pick-detail-v2.adoc[] include::pick-search-v2.adoc[] include::pick-similarity-v2.adoc[] \ No newline at end of file diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceV2.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceV2.java index ab240a5b..2e79324e 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceV2.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceV2.java @@ -2,34 +2,35 @@ import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember; import com.dreamypatisiel.devdevdev.domain.entity.Pick; +import com.dreamypatisiel.devdevdev.domain.entity.PickOption; import com.dreamypatisiel.devdevdev.domain.entity.enums.ContentStatus; +import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.policy.PickBestCommentsPolicy; import com.dreamypatisiel.devdevdev.domain.policy.PickPopularScorePolicy; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRecommendRepository; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRepository; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSearchDto; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort; +import com.dreamypatisiel.devdevdev.domain.repository.pick.*; import com.dreamypatisiel.devdevdev.domain.repository.pick.mybatis.PickMapper; import com.dreamypatisiel.devdevdev.domain.service.member.AnonymousMemberService; +import com.dreamypatisiel.devdevdev.exception.NotFoundException; import com.dreamypatisiel.devdevdev.global.common.TimeProvider; import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils; import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService; import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainSearchResponseV2; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponseV2; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.*; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; + +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE; +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_MESSAGE; @Service @Transactional(readOnly = true) @@ -77,6 +78,40 @@ public Slice findPicksMain(Pageable pageable, Long pickId, P return new SliceCustom<>(pickMainResponse, pageable, picks.hasNext(), totalElements); } + /** + * 픽픽픽 상세 조회 V2 + */ + @Transactional + @Override + public PickDetailResponseV2 findPickDetail(Long pickId, String anonymousMemberId, Authentication authentication) { + + // 익명 사용자 호출인지 확인 + AuthenticationMemberUtils.validateAnonymousMethodCall(authentication); + + // 익명 회원 조회 또는 생성 + AnonymousMember anonymousMember = anonymousMemberService.findOrCreateAnonymousMember(anonymousMemberId); + + // 픽픽픽 상세 조회(pickOption 페치조인) + Pick findPick = pickRepository.findPickWithPickOptionByPickId(pickId) + .orElseThrow(() -> new NotFoundException(INVALID_NOT_FOUND_PICK_MESSAGE)); + + // 픽픽픽 게시글의 승인 상태가 아니면 + if (!findPick.isTrueContentStatus(ContentStatus.APPROVAL)) { + throw new IllegalArgumentException(INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE); + } + + findPick.plusOneViewTotalCount(); // 조회수 증가 + findPick.changePopularScore(pickPopularScorePolicy); // 인기점수 계산 + + // 픽픽픽 옵션 가공 + Map pickDetailOptions = findPick.getPickOptions().stream() + .collect(Collectors.toMap(PickOption::getPickOptionType, + pickOption -> PickDetailOptionResponse.of(pickOption, findPick, anonymousMember))); + + // 픽픽픽 상세 + return PickDetailResponseV2.of(findPick, findPick.getMember(), anonymousMember, pickDetailOptions); + } + /** * 나도 고민했는데 픽픽픽 V2 */ diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceV2.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceV2.java index bab6fc90..18cf80b9 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceV2.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceV2.java @@ -2,7 +2,9 @@ import com.dreamypatisiel.devdevdev.domain.entity.Member; import com.dreamypatisiel.devdevdev.domain.entity.Pick; +import com.dreamypatisiel.devdevdev.domain.entity.PickOption; import com.dreamypatisiel.devdevdev.domain.entity.enums.ContentStatus; +import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.policy.PickBestCommentsPolicy; import com.dreamypatisiel.devdevdev.domain.policy.PickPopularScorePolicy; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickCommentRecommendRepository; @@ -10,11 +12,14 @@ import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSearchDto; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort; +import com.dreamypatisiel.devdevdev.exception.NotFoundException; import com.dreamypatisiel.devdevdev.domain.repository.pick.mybatis.PickMapper; import com.dreamypatisiel.devdevdev.global.common.MemberProvider; import com.dreamypatisiel.devdevdev.global.common.TimeProvider; import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService; import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailOptionResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainSearchResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponseV2; @@ -30,6 +35,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE; +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_MESSAGE; + @Service @Transactional(readOnly = true) public class MemberPickServiceV2 extends PickCommonService implements PickServiceV2 { @@ -72,6 +80,37 @@ public Slice findPicksMain(Pageable pageable, Long pickId, P return new SliceCustom<>(pickMainResponse, pageable, picks.hasNext(), totalElements); } + /** + * 픽픽픽 상세 조회 V2 + */ + @Transactional + @Override + public PickDetailResponseV2 findPickDetail(Long pickId, String anonymousMemberId, Authentication authentication) { + + // 회원 조회 + Member findMember = memberProvider.getMemberByAuthentication(authentication); + + // 픽픽픽 상세 조회(pickOption 페치조인) + Pick findPick = pickRepository.findPickWithPickOptionByPickId(pickId) + .orElseThrow(() -> new NotFoundException(INVALID_NOT_FOUND_PICK_MESSAGE)); + + // 픽픽픽 게시글의 승인 상태가 아니면 + if (!findPick.isTrueContentStatus(ContentStatus.APPROVAL)) { + throw new IllegalArgumentException(INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE); + } + + findPick.plusOneViewTotalCount(); // 조회수 증가 + findPick.changePopularScore(pickPopularScorePolicy); // 인기점수 계산 + + // 픽픽픽 옵션 가공 + Map pickDetailOptions = findPick.getPickOptions().stream() + .collect(Collectors.toMap(PickOption::getPickOptionType, + pickOption -> PickDetailOptionResponse.of(pickOption, findPick, findMember))); + + // 픽픽픽 상세 + return PickDetailResponseV2.of(findPick, findPick.getMember(), findMember, pickDetailOptions); + } + /** * 나도 고민했는데 픽픽픽 V2 */ diff --git a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickServiceV2.java b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickServiceV2.java index 49964a23..aa531fe8 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickServiceV2.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/domain/service/pick/PickServiceV2.java @@ -1,6 +1,7 @@ package com.dreamypatisiel.devdevdev.domain.service.pick; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainSearchResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponseV2; @@ -13,6 +14,8 @@ public interface PickServiceV2 extends PickService { Slice findPicksMain(Pageable pageable, Long pickId, PickSort pickSort, String anonymousMemberId, Authentication authentication); + PickDetailResponseV2 findPickDetail(Long pickId, String anonymousMemberId, Authentication authentication); + List findTop3SimilarPicksV2(Long pickId); Slice findPickMainSearch(Pageable pageable, Long pickId, Double searchScore, String keyword, diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickControllerV2.java b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickControllerV2.java index e646878a..961a9fd9 100644 --- a/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickControllerV2.java +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/controller/pick/PickControllerV2.java @@ -9,6 +9,7 @@ import com.dreamypatisiel.devdevdev.global.utils.HttpRequestUtils; import com.dreamypatisiel.devdevdev.web.controller.ApiVersion; import com.dreamypatisiel.devdevdev.web.dto.response.BasicResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainSearchResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.SimilarPickResponseV2; @@ -54,6 +55,18 @@ public ResponseEntity>> getPicksMain( return ResponseEntity.ok(BasicResponse.success(response)); } + @Operation(summary = "픽픽픽 상세 조회 V2", description = "픽픽픽 상세 페이지를 조회합니다.") + @GetMapping("/picks/{pickId}") + public ResponseEntity> getPickDetail(@PathVariable Long pickId, + @RequestHeader(value = HEADER_ANONYMOUS_MEMBER_ID, required = false) String anonymousMemberId) { + Authentication authentication = AuthenticationMemberUtils.getAuthentication(); + + PickServiceV2 pickService = (PickServiceV2) pickServiceStrategy.getPickService(ApiVersion.V2); + PickDetailResponseV2 response = pickService.findPickDetail(pickId, anonymousMemberId, authentication); + + return ResponseEntity.ok(BasicResponse.success(response)); + } + @Operation(summary = "나도 고민했는데 픽픽픽 V2", description = "픽픽픽 상세와 유사한 성격의 픽픽픽 3개를 추천합니다.") @GetMapping("picks/{pickId}/similarties") public ResponseEntity> getSimilarPicks(@PathVariable Long pickId) { diff --git a/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/pick/PickDetailResponseV2.java b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/pick/PickDetailResponseV2.java new file mode 100644 index 00000000..dc072dab --- /dev/null +++ b/src/main/java/com/dreamypatisiel/devdevdev/web/dto/response/pick/PickDetailResponseV2.java @@ -0,0 +1,79 @@ +package com.dreamypatisiel.devdevdev.web.dto.response.pick; + +import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember; +import com.dreamypatisiel.devdevdev.domain.entity.Member; +import com.dreamypatisiel.devdevdev.domain.entity.Pick; +import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; +import com.dreamypatisiel.devdevdev.web.dto.util.CommonResponseUtil; +import com.dreamypatisiel.devdevdev.web.dto.util.PickResponseUtils; +import com.dreamypatisiel.devdevdev.global.common.TimeProvider; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import java.time.LocalDateTime; +import java.util.Map; +import lombok.Builder; +import lombok.Data; + +@Data +public class PickDetailResponseV2 { + private final String userId; + private final String nickname; + + @JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = TimeProvider.DEFAULT_ZONE_ID) + private final LocalDateTime pickCreatedAt; + + private final String pickTitle; + private final long voteTotalCount; + private final long commentTotalCount; + private final Boolean isAuthor; + private final Boolean isVoted; + private final Map pickOptions; + + @Builder + public PickDetailResponseV2(String userId, String nickname, LocalDateTime pickCreatedAt, String pickTitle, + long voteTotalCount, long commentTotalCount, boolean isAuthor, boolean isVoted, + Map pickOptions) { + this.userId = userId; + this.nickname = nickname; + this.pickCreatedAt = pickCreatedAt; + this.pickTitle = pickTitle; + this.voteTotalCount = voteTotalCount; + this.commentTotalCount = commentTotalCount; + this.isAuthor = isAuthor; + this.isVoted = isVoted; + this.pickOptions = pickOptions; + } + + // 회원 전용 + public static PickDetailResponseV2 of(Pick pick, Member pickMember, Member member, + Map pickDetailOptions) { + return PickDetailResponseV2.builder() + .userId(CommonResponseUtil.sliceAndMaskEmail(pickMember.getEmail().getEmail())) + .nickname(pickMember.getNickname().getNickname()) + .pickCreatedAt(pick.getCreatedAt()) + .pickTitle(pick.getTitle().getTitle()) + .voteTotalCount(pick.getVoteTotalCount().getCount()) + .commentTotalCount(pick.getCommentTotalCount().getCount()) + .isAuthor(pick.isEqualMember(member)) + .isVoted(PickResponseUtils.isVotedMember(pick, member)) + .pickOptions(pickDetailOptions) + .build(); + } + + // 익명 회원 전용 + public static PickDetailResponseV2 of(Pick pick, Member pickMember, AnonymousMember anonymousMember, + Map pickDetailOptions) { + return PickDetailResponseV2.builder() + .isAuthor(false) + .pickCreatedAt(pick.getCreatedAt()) + .nickname(pickMember.getNickname().getNickname()) + .userId(CommonResponseUtil.sliceAndMaskEmail(pickMember.getEmail().getEmail())) + .pickTitle(pick.getTitle().getTitle()) + .voteTotalCount(pick.getVoteTotalCount().getCount()) + .commentTotalCount(pick.getCommentTotalCount().getCount()) + .pickOptions(pickDetailOptions) + .isVoted(PickResponseUtils.isVotedAnonymousMember(pick, anonymousMember)) + .build(); + } +} + diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceV2Test.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceV2Test.java index c4c208ae..1d209fca 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceV2Test.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/GuestPickServiceV2Test.java @@ -8,17 +8,22 @@ import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.entity.enums.Role; import com.dreamypatisiel.devdevdev.domain.entity.enums.SocialType; +import com.dreamypatisiel.devdevdev.domain.repository.member.AnonymousMemberRepository; import com.dreamypatisiel.devdevdev.domain.repository.member.MemberRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickOptionImageRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickOptionRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository; -import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort; -import com.dreamypatisiel.devdevdev.domain.service.member.AnonymousMemberService; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickVoteRepository; +import com.dreamypatisiel.devdevdev.exception.NotFoundException; import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; import com.dreamypatisiel.devdevdev.global.utils.AuthenticationMemberUtils; import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService; -import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailOptionImageResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailOptionResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2; +import com.dreamypatisiel.devdevdev.web.dto.util.CommonResponseUtil; +import jakarta.persistence.EntityManager; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -27,20 +32,15 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; import org.springframework.security.core.Authentication; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Map; -import static com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType.firstPickOption; -import static com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType.secondPickOption; -import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_APPROVAL_STATUS_PICK_MESSAGE; import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_MESSAGE; -import static com.dreamypatisiel.devdevdev.domain.service.pick.PickTestUtils.createPickOptionImage; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -57,9 +57,13 @@ class GuestPickServiceV2Test { @Autowired PickOptionImageRepository pickOptionImageRepository; @Autowired + PickVoteRepository pickVoteRepository; + @Autowired MemberRepository memberRepository; - @MockBean - AnonymousMemberService anonymousMemberService; + @Autowired + AnonymousMemberRepository anonymousMemberRepository; + @Autowired + EntityManager em; @MockBean EmbeddingsService embeddingsService; @@ -132,6 +136,126 @@ void findPicksMain() { ); } + @Test + @DisplayName("익명 회원이 픽픽픽 상세를 조회한다.") + void findPickDetail() { + // given + // 익명 회원 생성 + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + + String anonymousMemberId = "GA1.1.276672604.1715872960"; + AnonymousMember anonymousMember = AnonymousMember.builder() + .anonymousMemberId(anonymousMemberId) + .build(); + anonymousMemberRepository.save(anonymousMember); + + // 픽픽픽 작성 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + // 픽픽픽 생성 + Pick pick = createPick(new Title("픽픽픽 제목"), new Count(0), new Count(5), new Count(1), new Count(0), member, + ContentStatus.APPROVAL); + pickRepository.save(pick); + + // 픽픽픽 옵션 생성 + PickOption firstPickOption = createPickOption(pick, new Title("픽픽픽 옵션1"), new PickOptionContents("픽픽픽 옵션1 내용"), + new Count(1), PickOptionType.firstPickOption); + PickOption secondPickOption = createPickOption(pick, new Title("픽픽픽 옵션2"), new PickOptionContents("픽픽픽 옵션2 내용"), + new Count(0), PickOptionType.secondPickOption); + pickOptionRepository.saveAll(List.of(firstPickOption, secondPickOption)); + + // 픽픽픽 옵션 이미지 생성 + PickOptionImage firstPickOptionImage = createPickOptionImage("이미지1", "http://iamge1.png", firstPickOption); + PickOptionImage secondPickOptionImage = createPickOptionImage("이미지2", "http://iamge2.png", secondPickOption); + pickOptionImageRepository.saveAll(List.of(firstPickOptionImage, secondPickOptionImage)); + + // 픽픽픽 옵션 투표 여부 + PickVote pickVote = createPickVote(anonymousMember, firstPickOption, pick); + pickVoteRepository.save(pickVote); + + em.flush(); + em.clear(); + + // when + PickDetailResponseV2 pickDetail = guestPickServiceV2.findPickDetail(pick.getId(), anonymousMemberId, + authentication); + + // then + assertThat(pickDetail).isNotNull(); + assertAll( + () -> assertThat(pickDetail.getUserId()).isEqualTo( + CommonResponseUtil.sliceAndMaskEmail(member.getEmail().getEmail())), + () -> assertThat(pickDetail.getNickname()).isEqualTo(member.getNickname().getNickname()), + () -> assertThat(pickDetail.getPickTitle()).isEqualTo("픽픽픽 제목"), + () -> assertThat(pickDetail.getCommentTotalCount()).isEqualTo(5), + () -> assertThat(pickDetail.getVoteTotalCount()).isEqualTo(1), + () -> assertThat(pickDetail.getIsAuthor()).isEqualTo(false) + ); + + Map pickOptions = pickDetail.getPickOptions(); + PickDetailOptionResponse findFirstPickOptionResponse = pickOptions.get(PickOptionType.firstPickOption); + PickDetailOptionResponse findSecondPickOptionResponse = pickOptions.get(PickOptionType.secondPickOption); + + PickOption findFirstPickOption = pickOptionRepository.findById(findFirstPickOptionResponse.getId()).get(); + assertThat(findFirstPickOptionResponse).isNotNull(); + assertAll( + () -> assertThat(findFirstPickOptionResponse.getId()).isEqualTo(findFirstPickOption.getId()), + () -> assertThat(findFirstPickOptionResponse.getTitle()).isEqualTo("픽픽픽 옵션1"), + () -> assertThat(findFirstPickOptionResponse.getIsPicked()).isEqualTo(true), + () -> assertThat(findFirstPickOptionResponse.getPercent()).isEqualTo(100), + () -> assertThat(findFirstPickOptionResponse.getContent()).isEqualTo("픽픽픽 옵션1 내용"), + () -> assertThat(findFirstPickOptionResponse.getVoteTotalCount()).isEqualTo(1) + ); + + List findFirstPickOptionPickOptionImagesResponse = findFirstPickOptionResponse.getPickDetailOptionImages(); + PickOptionImage findFirstPickOptionImage = findFirstPickOption.getPickOptionImages().get(0); + assertThat(findFirstPickOptionPickOptionImagesResponse).hasSize(1) + .extracting("id", "imageUrl") + .containsExactly( + tuple(findFirstPickOptionImage.getId(), "http://iamge1.png") + ); + + PickOption findfirstPickOption = pickOptionRepository.findById(findSecondPickOptionResponse.getId()).get(); + assertThat(findSecondPickOptionResponse).isNotNull(); + assertAll( + () -> assertThat(findSecondPickOptionResponse.getId()).isEqualTo(findfirstPickOption.getId()), + () -> assertThat(findSecondPickOptionResponse.getTitle()).isEqualTo("픽픽픽 옵션2"), + () -> assertThat(findSecondPickOptionResponse.getIsPicked()).isEqualTo(false), + () -> assertThat(findSecondPickOptionResponse.getPercent()).isEqualTo(0), + () -> assertThat(findSecondPickOptionResponse.getContent()).isEqualTo("픽픽픽 옵션2 내용"), + () -> assertThat(findSecondPickOptionResponse.getVoteTotalCount()).isEqualTo(0) + ); + + List findfirstPickOptionPickOptionImagesResponse = findSecondPickOptionResponse.getPickDetailOptionImages(); + PickOptionImage findsecondPickOptionImage = secondPickOption.getPickOptionImages().get(0); + assertThat(findfirstPickOptionPickOptionImagesResponse).hasSize(1) + .extracting("id", "imageUrl") + .containsExactly( + tuple(findsecondPickOptionImage.getId(), "http://iamge2.png") + ); + } + + + @Test + @DisplayName("익명 회원이 픽픽픽 상세 조회할 때 픽픽픽이 없으면 예외가 발생한다. V2") + void findPickDetailNotFoundPickDetail() { + // given + String anonymousMemberId = "GA1.1.276672604.1715872960"; + AnonymousMember anonymousMember = AnonymousMember.builder() + .anonymousMemberId(anonymousMemberId) + .build(); + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn(AuthenticationMemberUtils.ANONYMOUS_USER); + + // when // then + assertThatThrownBy(() -> guestPickServiceV2.findPickDetail(0L, anonymousMemberId, authentication)) + .isInstanceOf(NotFoundException.class) + .hasMessage(INVALID_NOT_FOUND_PICK_MESSAGE); + } + private Pick createPick(Title title, Count viewTotalCount, Count commentTotalCount, Count voteTotalCount, Count poplarScore, Member member, ContentStatus contentStatus) { return Pick.builder() diff --git a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceV2Test.java b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceV2Test.java index d39a0f48..86624eba 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceV2Test.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/domain/service/pick/MemberPickServiceV2Test.java @@ -1,14 +1,6 @@ package com.dreamypatisiel.devdevdev.domain.service.pick; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; - -import com.dreamypatisiel.devdevdev.domain.entity.AnonymousMember; -import com.dreamypatisiel.devdevdev.domain.entity.Member; -import com.dreamypatisiel.devdevdev.domain.entity.Pick; -import com.dreamypatisiel.devdevdev.domain.entity.PickOption; -import com.dreamypatisiel.devdevdev.domain.entity.PickOptionImage; -import com.dreamypatisiel.devdevdev.domain.entity.PickVote; +import com.dreamypatisiel.devdevdev.domain.entity.*; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; import com.dreamypatisiel.devdevdev.domain.entity.embedded.PickOptionContents; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; @@ -21,22 +13,35 @@ import com.dreamypatisiel.devdevdev.domain.repository.pick.PickOptionImageRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickOptionRepository; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickRepository; -import com.dreamypatisiel.devdevdev.global.common.MemberProvider; +import com.dreamypatisiel.devdevdev.domain.repository.pick.PickVoteRepository; +import com.dreamypatisiel.devdevdev.exception.NotFoundException; import com.dreamypatisiel.devdevdev.global.security.oauth2.model.SocialMemberDto; -import com.dreamypatisiel.devdevdev.openai.embeddings.EmbeddingsService; +import com.dreamypatisiel.devdevdev.global.security.oauth2.model.UserPrincipal; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailOptionResponse; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickDetailResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2; +import com.dreamypatisiel.devdevdev.web.dto.util.CommonResponseUtil; import jakarta.persistence.EntityManager; -import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.Map; + +import static com.dreamypatisiel.devdevdev.domain.exception.PickExceptionMessage.INVALID_NOT_FOUND_PICK_MESSAGE; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; + @SpringBootTest @Transactional class MemberPickServiceV2Test { @@ -52,13 +57,11 @@ class MemberPickServiceV2Test { @Autowired PickOptionImageRepository pickOptionImageRepository; @Autowired + PickVoteRepository pickVoteRepository; + @Autowired PickPopularScorePolicy pickPopularScorePolicy; @Autowired EntityManager em; - @MockBean - MemberProvider memberProvider; - @MockBean - EmbeddingsService embeddingsService; String userId = "dreamy5patisiel"; String name = "꿈빛파티시엘"; @@ -77,6 +80,12 @@ void findPicksMain() { Member member = Member.createMemberBy(socialMemberDto); memberRepository.save(member); + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + // 픽픽픽 생성 Pick pick = createPick(new Title("픽픽픽 제목"), new Count(0), new Count(0), new Count(1), new Count(0), member, ContentStatus.APPROVAL); @@ -97,7 +106,8 @@ void findPicksMain() { Pageable pageable = PageRequest.of(0, 10); // when - Slice picksMain = memberPickServiceV2.findPicksMain(pageable, null, null, null, null); + Slice picksMain = memberPickServiceV2.findPicksMain(pageable, null, null, + null, authentication); // then Pick findPick = pickRepository.findById(pick.getId()).get(); @@ -122,6 +132,107 @@ void findPicksMain() { ); } + @Test + @DisplayName("회원이 픽픽픽 상세를 조회한다. V2") + void findPickDetail() { + // given + // 회원 생성 + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // 픽픽픽 생성 (투표수 1, 댓글수 5) + Pick pick = createPick(new Title("픽픽픽 제목"), new Count(0), new Count(5), new Count(1), new Count(0), member, + ContentStatus.APPROVAL); + pickRepository.save(pick); + + // 픽픽픽 옵션 생성 + PickOption firstPickOption = createPickOption(pick, new Title("픽픽픽 옵션1"), new PickOptionContents("픽픽픽 옵션1 내용"), + new Count(1), PickOptionType.firstPickOption); + PickOption secondPickOption = createPickOption(pick, new Title("픽픽픽 옵션2"), new PickOptionContents("픽픽픽 옵션2 내용"), + new Count(0), PickOptionType.secondPickOption); + pickOptionRepository.saveAll(List.of(firstPickOption, secondPickOption)); + + // 픽픽픽 옵션 이미지 생성 + PickOptionImage firstPickOptionImage = createPickOptionImage("이미지1", "http://image1.png", firstPickOption); + PickOptionImage secondPickOptionImage = createPickOptionImage("이미지2", "http://image2.png", secondPickOption); + pickOptionImageRepository.saveAll(List.of(firstPickOptionImage, secondPickOptionImage)); + + // 픽픽픽 옵션 투표 여부 + PickVote pickVote = createPickVote(member, firstPickOption, pick); + pickVoteRepository.save(pickVote); + + em.flush(); + + // when + PickDetailResponseV2 pickDetail = memberPickServiceV2.findPickDetail(pick.getId(), null, authentication); + + // then + assertThat(pickDetail).isNotNull(); + assertAll( + () -> assertThat(pickDetail.getUserId()).isEqualTo( + CommonResponseUtil.sliceAndMaskEmail(member.getEmail().getEmail())), + () -> assertThat(pickDetail.getNickname()).isEqualTo(member.getNickname().getNickname()), + () -> assertThat(pickDetail.getPickTitle()).isEqualTo("픽픽픽 제목"), + () -> assertThat(pickDetail.getVoteTotalCount()).isEqualTo(1L), + () -> assertThat(pickDetail.getCommentTotalCount()).isEqualTo(5L), + () -> assertThat(pickDetail.getIsAuthor()).isEqualTo(true), + () -> assertThat(pickDetail.getIsVoted()).isEqualTo(true) + ); + + Map pickOptions = pickDetail.getPickOptions(); + PickDetailOptionResponse findFirstPickOptionResponse = pickOptions.get(PickOptionType.firstPickOption); + PickDetailOptionResponse findSecondPickOptionResponse = pickOptions.get(PickOptionType.secondPickOption); + + PickOption findFirstPickOption = pickOptionRepository.findById(findFirstPickOptionResponse.getId()).get(); + assertThat(findFirstPickOptionResponse).isNotNull(); + assertAll( + () -> assertThat(findFirstPickOptionResponse.getTitle()).isEqualTo("픽픽픽 옵션1"), + () -> assertThat(findFirstPickOptionResponse.getIsPicked()).isEqualTo(true), + () -> assertThat(findFirstPickOptionResponse.getPercent()).isEqualTo(100), + () -> assertThat(findFirstPickOptionResponse.getContent()).isEqualTo("픽픽픽 옵션1 내용"), + () -> assertThat(findFirstPickOptionResponse.getVoteTotalCount()).isEqualTo(1L), + () -> assertThat(findFirstPickOption.getPickOptionImages()).hasSize(1) + ); + + PickOption findSecondPickOption = pickOptionRepository.findById(findSecondPickOptionResponse.getId()).get(); + assertThat(findSecondPickOptionResponse).isNotNull(); + assertAll( + () -> assertThat(findSecondPickOptionResponse.getTitle()).isEqualTo("픽픽픽 옵션2"), + () -> assertThat(findSecondPickOptionResponse.getIsPicked()).isEqualTo(false), + () -> assertThat(findSecondPickOptionResponse.getPercent()).isEqualTo(0), + () -> assertThat(findSecondPickOptionResponse.getContent()).isEqualTo("픽픽픽 옵션2 내용"), + () -> assertThat(findSecondPickOptionResponse.getVoteTotalCount()).isEqualTo(0L), + () -> assertThat(findSecondPickOption.getPickOptionImages()).hasSize(1) + ); + } + + @Test + @DisplayName("회원이 픽픽픽 상세 조회할 때 픽픽픽이 없으면 예외가 발생한다. V2") + void findPickDetailNotFoundPickDetail() { + // given + SocialMemberDto socialMemberDto = createSocialDto(userId, name, nickname, password, email, socialType, role); + Member member = Member.createMemberBy(socialMemberDto); + memberRepository.save(member); + + UserPrincipal userPrincipal = UserPrincipal.createByMember(member); + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(new OAuth2AuthenticationToken(userPrincipal, userPrincipal.getAuthorities(), + userPrincipal.getSocialType().name())); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + // when // then + assertThatThrownBy(() -> memberPickServiceV2.findPickDetail(0L, null, authentication)) + .isInstanceOf(NotFoundException.class) + .hasMessage(INVALID_NOT_FOUND_PICK_MESSAGE); + } + private Pick createPick(Title title, Count viewTotalCount, Count commentTotalCount, Count voteTotalCount, Count poplarScore, Member member, ContentStatus contentStatus) { return Pick.builder() diff --git a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickControllerV2DocsTest.java b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickControllerV2DocsTest.java index 8c4adfb9..18afebc3 100644 --- a/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickControllerV2DocsTest.java +++ b/src/test/java/com/dreamypatisiel/devdevdev/web/docs/PickControllerV2DocsTest.java @@ -28,11 +28,13 @@ import com.dreamypatisiel.devdevdev.domain.entity.embedded.Count; import com.dreamypatisiel.devdevdev.domain.entity.embedded.Title; +import com.dreamypatisiel.devdevdev.domain.entity.enums.PickOptionType; import com.dreamypatisiel.devdevdev.domain.repository.pick.PickSort; import com.dreamypatisiel.devdevdev.domain.service.pick.GuestPickServiceV2; import com.dreamypatisiel.devdevdev.domain.service.pick.MemberPickServiceV2; import com.dreamypatisiel.devdevdev.global.constant.SecurityConstant; import com.dreamypatisiel.devdevdev.web.dto.SliceCustom; +import com.dreamypatisiel.devdevdev.web.dto.response.pick.*; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainOptionResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainResponseV2; import com.dreamypatisiel.devdevdev.web.dto.response.pick.PickMainSearchResponseV2; @@ -49,6 +51,30 @@ import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.test.web.servlet.ResultActions; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static com.dreamypatisiel.devdevdev.global.constant.SecurityConstant.AUTHORIZATION_HEADER; +import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.authenticationType; +import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.pickSortType; +import static com.dreamypatisiel.devdevdev.web.docs.format.ApiDocsFormatGenerator.yearMonthDateTimeType; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; class PickControllerV2DocsTest extends SupportControllerDocsTest { @@ -255,6 +281,141 @@ void getSimilarPicks() throws Exception { )); } + @Test + @DisplayName("회원이 픽픽픽 상세를 조회한다. V2") + void getPickDetail() throws Exception { + // given + Long pickId = 1L; + + // Mock 데이터 생성 + PickDetailOptionImageResponse image1 = PickDetailOptionImageResponse.builder() + .id(1L) + .imageUrl("http://image1.png") + .build(); + + PickDetailOptionImageResponse image2 = PickDetailOptionImageResponse.builder() + .id(2L) + .imageUrl("http://image2.png") + .build(); + + PickDetailOptionResponse firstOption = PickDetailOptionResponse.builder() + .id(1L) + .title("픽옵션1") + .isPicked(true) + .percent(new BigDecimal("60")) + .content("픽콘텐츠1") + .voteTotalCount(3L) + .pickDetailOptionImagesResponse(List.of(image1)) + .build(); + + PickDetailOptionResponse secondOption = PickDetailOptionResponse.builder() + .id(2L) + .title("픽옵션2") + .isPicked(false) + .percent(new BigDecimal("40")) + .content("픽콘텐츠2") + .voteTotalCount(2L) + .pickDetailOptionImagesResponse(List.of(image2)) + .build(); + + Map pickOptions = Map.of( + PickOptionType.firstPickOption, firstOption, + PickOptionType.secondPickOption, secondOption + ); + + PickDetailResponseV2 response = PickDetailResponseV2.builder() + .userId("dre***@gmail.com") + .nickname("꿈빛파티시엘") + .pickCreatedAt(LocalDateTime.of(2024, 1, 1, 12, 0, 0)) + .pickTitle("픽픽픽 제목") + .voteTotalCount(5L) + .commentTotalCount(10L) + .isAuthor(true) + .isVoted(true) + .pickOptions(pickOptions) + .build(); + + when(memberPickServiceV2.findPickDetail(anyLong(), any(), any(Authentication.class))) + .thenReturn(response); + + // when // then + ResultActions actions = mockMvc.perform(get("/devdevdev/api/v2/picks/{pickId}", pickId) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER, SecurityConstant.BEARER_PREFIX + accessToken) + .characterEncoding(StandardCharsets.UTF_8)) + .andDo(print()) + .andExpect(status().isOk()); + + // docs + actions.andDo(document("pick-detail-v2", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName(AUTHORIZATION_HEADER).optional().description("Bearer 엑세스 토큰"), + headerWithName("Anonymous-Member-Id").optional().description("익명 회원 아이디") + ), + pathParameters( + parameterWithName("pickId").description("픽픽픽 아이디") + ), + responseFields( + fieldWithPath("resultType").type(STRING).description("응답 결과"), + fieldWithPath("data").type(OBJECT).description("응답 데이터"), + + fieldWithPath("data.nickname").type(STRING).description("픽픽픽 작성자 닉네임"), + fieldWithPath("data.userId").type(STRING).description("픽픽픽 작성자 아이디"), + fieldWithPath("data.pickCreatedAt").type(STRING).description("픽픽픽 생성 일시") + .attributes(yearMonthDateTimeType()), + fieldWithPath("data.pickTitle").type(STRING).description("픽픽픽 제목"), + fieldWithPath("data.voteTotalCount").type(NUMBER).description("픽픽픽 전체 투표 수 (NEW)"), + fieldWithPath("data.commentTotalCount").type(NUMBER).description("픽픽픽 전체 댓글 수 (NEW)"), + fieldWithPath("data.isAuthor").type(BOOLEAN).description("현재 로그인한 회원이 픽픽픽 작성자 여부"), + fieldWithPath("data.isVoted").type(BOOLEAN).description("픽픽픽 투표 여부"), + + fieldWithPath("data.pickOptions").type(OBJECT).description("픽픽픽 옵션 객체"), + + fieldWithPath("data.pickOptions.firstPickOption").type(OBJECT).description("픽픽픽 첫번째 옵션 객체"), + fieldWithPath("data.pickOptions.firstPickOption.id").type(NUMBER) + .description("첫 번째 픽픽픽 옵션 아이디"), + fieldWithPath("data.pickOptions.firstPickOption.title").type(STRING) + .description("첫 번째 픽픽픽 옵션 제목"), + fieldWithPath("data.pickOptions.firstPickOption.isPicked").type(BOOLEAN) + .description("첫 번째 픽픽픽 옵션 투표 여부"), + fieldWithPath("data.pickOptions.firstPickOption.percent").type(NUMBER) + .description("첫 번째 픽픽픽 옵션 득표율(%)"), + fieldWithPath("data.pickOptions.firstPickOption.content").type(STRING) + .description("첫 번째 픽픽픽 옵션 내용"), + fieldWithPath("data.pickOptions.firstPickOption.voteTotalCount").type(NUMBER) + .description("첫 번째 픽픽픽 옵션 득표수"), + fieldWithPath("data.pickOptions.firstPickOption.pickDetailOptionImages").type(ARRAY) + .description("첫 번째 픽픽픽 옵션 이미지 배열"), + fieldWithPath("data.pickOptions.firstPickOption.pickDetailOptionImages.[].id").type(NUMBER) + .description("첫 번째 픽픽픽 옵션 이미지 아이디"), + fieldWithPath("data.pickOptions.firstPickOption.pickDetailOptionImages.[].imageUrl").type( + STRING).description("첫 번째 픽픽픽 옵션 이미지 url"), + + fieldWithPath("data.pickOptions.secondPickOption").type(OBJECT).description("픽픽픽 두번째 옵션 객체"), + fieldWithPath("data.pickOptions.secondPickOption.id").type(NUMBER) + .description("두 번째 픽픽픽 옵션 아이디"), + fieldWithPath("data.pickOptions.secondPickOption.title").type(STRING) + .description("두 번째 픽픽픽 옵션 제목"), + fieldWithPath("data.pickOptions.secondPickOption.isPicked").type(BOOLEAN) + .description("두 번째 픽픽픽 옵션 투표 여부"), + fieldWithPath("data.pickOptions.secondPickOption.percent").type(NUMBER) + .description("두 번째 픽픽픽 옵션 득표율(%)"), + fieldWithPath("data.pickOptions.secondPickOption.content").type(STRING) + .description("두 번째 픽픽픽 옵션 내용"), + fieldWithPath("data.pickOptions.secondPickOption.voteTotalCount").type(NUMBER) + .description("두 번째 픽픽픽 옵션 득표수"), + fieldWithPath("data.pickOptions.secondPickOption.pickDetailOptionImages").type(ARRAY) + .description("두 번째 픽픽픽 옵션 이미지 배열"), + fieldWithPath("data.pickOptions.secondPickOption.pickDetailOptionImages.[].id").type(NUMBER) + .description("두 번째 픽픽픽 옵션 이미지 아이디"), + fieldWithPath("data.pickOptions.secondPickOption.pickDetailOptionImages.[].imageUrl").type( + STRING).description("두 번째 픽픽픽 옵션 이미지 url") + ) + )); + } + @Test @DisplayName("회원이 픽픽픽 검색을 조회한다.") void searchPicksMain() throws Exception {