From a9a370c0ddc35cac3bb5766617759cbfc090d1f5 Mon Sep 17 00:00:00 2001 From: songhyeonpk Date: Fri, 16 May 2025 15:46:25 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=ED=95=9C=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=ED=94=BD=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/user-api.adoc | 38 ++- .../LoadMyBookmarkPostController.java | 43 ++++ .../user/UserDomainPersistenceAdapter.java | 17 +- .../repository/BookmarkCustomRepository.java | 10 + .../BookmarkCustomRepositoryImpl.java | 42 ++++ .../repository/BookmarkRepository.java | 3 +- .../repository/PostCustomRepositoryImpl.java | 2 +- .../in/user/LoadMyBookmarkPostUseCase.java | 11 + .../persistence/user/LoadBookmarkPort.java | 12 + .../user/LoadPostUserDomainPort.java | 3 + .../query/FindBookmarksByPagingQuery.java | 21 ++ .../user/LoadMyBookmarkPostService.java | 69 ++++++ .../server/user/LoadMyBookmarkPostTest.java | 217 ++++++++++++++++++ 13 files changed, 483 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/ftm/server/adapter/in/web/user/controller/LoadMyBookmarkPostController.java create mode 100644 src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkCustomRepository.java create mode 100644 src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkCustomRepositoryImpl.java create mode 100644 src/main/java/com/ftm/server/application/port/in/user/LoadMyBookmarkPostUseCase.java create mode 100644 src/main/java/com/ftm/server/application/port/out/persistence/user/LoadBookmarkPort.java create mode 100644 src/main/java/com/ftm/server/application/query/FindBookmarksByPagingQuery.java create mode 100644 src/main/java/com/ftm/server/application/service/user/LoadMyBookmarkPostService.java create mode 100644 src/test/java/com/ftm/server/user/LoadMyBookmarkPostTest.java diff --git a/src/docs/asciidoc/user-api.adoc b/src/docs/asciidoc/user-api.adoc index 2ad3d7f..f2302bd 100644 --- a/src/docs/asciidoc/user-api.adoc +++ b/src/docs/asciidoc/user-api.adoc @@ -207,7 +207,8 @@ include::{snippetsDir}/createBookmark/3/http-response.adoc[] 마이페이지 > 작성한 게시글 목록 > 더보기 버튼 클릭 시 내가 작성한 유저픽 게시글 목록을 조회하는 api 입니다. + 무한 스크롤, 더보기 형식으로 조회하여 모든 데이터 개수와 번호를 부여하는 Page 방식이 아닌, + -다음 페이지의 데이터가 존재하는지의 여부를 함께 응답하는 Slice 방식을 사용했습니다. +다음 페이지의 데이터가 존재하는지의 여부를 함께 응답하는 Slice 방식을 사용했습니다. + +최신순으로 조회합니다. ==== Request include::{snippetsDir}/loadMyPosts/1/http-request.adoc[] @@ -232,4 +233,37 @@ include::{snippetsDir}/loadMyPosts/3/http-response.adoc[] 실패 3. 요청 페이지당 개수 유효성 검증에 실패할 경우 -include::{snippetsDir}/loadMyPosts/4/http-response.adoc[] \ No newline at end of file +include::{snippetsDir}/loadMyPosts/4/http-response.adoc[] + + +=== **12. 내가 북마크한 유저픽 게시글 목록 조회** + +마이페이지 > 북마크한 게시글 목록 > 더보기 버튼 클릭 시 내가 작성한 유저픽 게시글 목록을 조회하는 api 입니다. + +무한 스크롤, 더보기 형식으로 조회하여 모든 데이터 개수와 번호를 부여하는 Page 방식이 아닌, + +다음 페이지의 데이터가 존재하는지의 여부를 함께 응답하는 Slice 방식을 사용했습니다. + +최신순으로 조회합니다. + +==== Request +include::{snippetsDir}/loadMyBookmarkPosts/1/http-request.adoc[] + +==== Request Query Parameter Fields +include::{snippetsDir}/loadMyBookmarkPosts/1/query-parameters.adoc[] + +==== 성공 Response +include::{snippetsDir}/loadMyBookmarkPosts/1/http-response.adoc[] + +==== Response Body Fields +include::{snippetsDir}/loadMyBookmarkPosts/1/response-fields.adoc[] + +==== 실패 Response +실패 1. 인증되지 않은 유저인 경우 + +include::{snippetsDir}/loadMyBookmarkPosts/2/http-response.adoc[] + +실패 2. 요청 페이지 번호 유효성 검증에 실패할 경우 + +include::{snippetsDir}/loadMyBookmarkPosts/3/http-response.adoc[] + +실패 3. 요청 페이지당 개수 유효성 검증에 실패할 경우 + +include::{snippetsDir}/loadMyBookmarkPosts/4/http-response.adoc[] \ No newline at end of file diff --git a/src/main/java/com/ftm/server/adapter/in/web/user/controller/LoadMyBookmarkPostController.java b/src/main/java/com/ftm/server/adapter/in/web/user/controller/LoadMyBookmarkPostController.java new file mode 100644 index 0000000..d164ccc --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/in/web/user/controller/LoadMyBookmarkPostController.java @@ -0,0 +1,43 @@ +package com.ftm.server.adapter.in.web.user.controller; + +import com.ftm.server.adapter.in.web.user.dto.response.LoadMyPostsResponse; +import com.ftm.server.application.port.in.user.LoadMyBookmarkPostUseCase; +import com.ftm.server.application.query.FindBookmarksByPagingQuery; +import com.ftm.server.application.vo.post.PostPagingVo; +import com.ftm.server.common.exception.CustomException; +import com.ftm.server.common.response.ApiResponse; +import com.ftm.server.common.response.enums.ErrorResponseCode; +import com.ftm.server.common.response.enums.SuccessResponseCode; +import com.ftm.server.infrastructure.security.UserPrincipal; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class LoadMyBookmarkPostController { + + private final LoadMyBookmarkPostUseCase loadMyBookmarkPostUseCase; + + @GetMapping("/api/users/me/bookmarks") + public ResponseEntity> loadMyBookmarkPosts( + @AuthenticationPrincipal UserPrincipal userPrincipal, + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "5") int size) { + // 요청 페이징 데이터 유효성 검증 + if (page < 0) throw new CustomException(ErrorResponseCode.BAD_REQUEST_PAGING_INDEX_RANGE); + if (size < 1 || size > 10) + throw new CustomException(ErrorResponseCode.BAD_REQUEST_PAGING_SIZE_RANGE); + + PostPagingVo vo = + loadMyBookmarkPostUseCase.execute( + FindBookmarksByPagingQuery.of(userPrincipal.getId(), page, size)); + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(SuccessResponseCode.OK, LoadMyPostsResponse.from(vo))); + } +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java b/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java index 06c301d..5bc1a12 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/adapter/user/UserDomainPersistenceAdapter.java @@ -41,7 +41,8 @@ public class UserDomainPersistenceAdapter DeleteBookmarkPort, SaveBookmarkPort, CheckBookmarkPort, - CheckPostPort { + CheckPostPort, + LoadBookmarkPort { // repository private final EmailVerificationLogsRepository emailVerificationLogsRepository; @@ -202,6 +203,13 @@ public Slice loadPostsByUserIdWithPaging(FindPostsByPagingQuery query) { return postRepository.findAllByUserIdWithPaging(query).map(postMapper::toDomainEntity); } + @Override + public List loadPostsByIds(FindByIdsQuery query) { + return postRepository.findAllById(query.getIds()).stream() + .map(postMapper::toDomainEntity) + .toList(); + } + @Override public List loadRepresentativeImagesByPostIds(FindByIdsQuery query) { return postImageRepository.findRepresentativeImagesByPostIdIn(query).stream() @@ -272,4 +280,11 @@ public Boolean checkIfBookmarkExists(FindBookmarkByUserIdAndPostIdQuery query) { public Boolean checksPostById(FindByPostIdQuery query) { return postRepository.existsById(query.getPostId()); } + + @Override + public Slice loadBookmarksByUserIdWithPaging(FindBookmarksByPagingQuery query) { + return bookmarkRepository + .findAllByUserIdWithPaging(query) + .map(bookmarkMapper::toDomainEntity); + } } diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkCustomRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkCustomRepository.java new file mode 100644 index 0000000..1da873e --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkCustomRepository.java @@ -0,0 +1,10 @@ +package com.ftm.server.adapter.out.persistence.repository; + +import com.ftm.server.adapter.out.persistence.model.BookmarkJpaEntity; +import com.ftm.server.application.query.FindBookmarksByPagingQuery; +import org.springframework.data.domain.Slice; + +public interface BookmarkCustomRepository { + + Slice findAllByUserIdWithPaging(FindBookmarksByPagingQuery query); +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkCustomRepositoryImpl.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkCustomRepositoryImpl.java new file mode 100644 index 0000000..3c1f1be --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkCustomRepositoryImpl.java @@ -0,0 +1,42 @@ +package com.ftm.server.adapter.out.persistence.repository; + +import static com.ftm.server.adapter.out.persistence.model.QBookmarkJpaEntity.bookmarkJpaEntity; + +import com.ftm.server.adapter.out.persistence.model.BookmarkJpaEntity; +import com.ftm.server.application.query.FindBookmarksByPagingQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class BookmarkCustomRepositoryImpl implements BookmarkCustomRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public Slice findAllByUserIdWithPaging(FindBookmarksByPagingQuery query) { + Pageable pageable = query.getPageable(); + List content = + queryFactory + .selectFrom(bookmarkJpaEntity) + .where(bookmarkJpaEntity.user.id.eq(query.getUserId())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) // 한 개 더 가져와서 hasNext 판별 + .orderBy(bookmarkJpaEntity.createdAt.desc(), bookmarkJpaEntity.id.desc()) + .fetch(); + + List result = content; + + boolean hasNext = content.size() > pageable.getPageSize(); + if (hasNext) { + result = content.subList(0, pageable.getPageSize()); // 초과분 제거 + } + + return new SliceImpl<>(result, pageable, hasNext); + } +} diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkRepository.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkRepository.java index ed3eaab..0fafa45 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkRepository.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/BookmarkRepository.java @@ -7,7 +7,8 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface BookmarkRepository extends JpaRepository { +public interface BookmarkRepository + extends JpaRepository, BookmarkCustomRepository { @Modifying @Query("DELETE FROM BookmarkJpaEntity b WHERE b.user.id in (:userIds)") void deleteAllByUserIdList(@Param("userIds") List userIds); diff --git a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostCustomRepositoryImpl.java b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostCustomRepositoryImpl.java index a47d30c..1ace775 100644 --- a/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostCustomRepositoryImpl.java +++ b/src/main/java/com/ftm/server/adapter/out/persistence/repository/PostCustomRepositoryImpl.java @@ -39,7 +39,7 @@ public Slice findAllByUserIdWithPaging(FindPostsByPagingQuery que .where(postJpaEntity.user.id.eq(query.getUserId())) .offset(pageable.getOffset()) .limit(pageable.getPageSize() + 1) // 한 개 더 가져와서 hasNext 판별 - .orderBy(postJpaEntity.createdAt.desc()) + .orderBy(postJpaEntity.createdAt.desc(), postJpaEntity.id.desc()) .fetch(); List result = content; diff --git a/src/main/java/com/ftm/server/application/port/in/user/LoadMyBookmarkPostUseCase.java b/src/main/java/com/ftm/server/application/port/in/user/LoadMyBookmarkPostUseCase.java new file mode 100644 index 0000000..6100fd2 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/in/user/LoadMyBookmarkPostUseCase.java @@ -0,0 +1,11 @@ +package com.ftm.server.application.port.in.user; + +import com.ftm.server.application.query.FindBookmarksByPagingQuery; +import com.ftm.server.application.vo.post.PostPagingVo; +import com.ftm.server.common.annotation.UseCase; + +@UseCase +public interface LoadMyBookmarkPostUseCase { + + PostPagingVo execute(FindBookmarksByPagingQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadBookmarkPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadBookmarkPort.java new file mode 100644 index 0000000..90d94a4 --- /dev/null +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadBookmarkPort.java @@ -0,0 +1,12 @@ +package com.ftm.server.application.port.out.persistence.user; + +import com.ftm.server.application.query.FindBookmarksByPagingQuery; +import com.ftm.server.common.annotation.Port; +import com.ftm.server.domain.entity.Bookmark; +import org.springframework.data.domain.Slice; + +@Port +public interface LoadBookmarkPort { + + Slice loadBookmarksByUserIdWithPaging(FindBookmarksByPagingQuery query); +} diff --git a/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadPostUserDomainPort.java b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadPostUserDomainPort.java index d2ec4ca..e52bb50 100644 --- a/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadPostUserDomainPort.java +++ b/src/main/java/com/ftm/server/application/port/out/persistence/user/LoadPostUserDomainPort.java @@ -1,5 +1,6 @@ package com.ftm.server.application.port.out.persistence.user; +import com.ftm.server.application.query.FindByIdsQuery; import com.ftm.server.application.query.FindByUserIdQuery; import com.ftm.server.application.query.FindPostsByPagingQuery; import com.ftm.server.domain.entity.Post; @@ -11,4 +12,6 @@ public interface LoadPostUserDomainPort { List loadPostListByUser(FindByUserIdQuery query); Slice loadPostsByUserIdWithPaging(FindPostsByPagingQuery query); + + List loadPostsByIds(FindByIdsQuery query); } diff --git a/src/main/java/com/ftm/server/application/query/FindBookmarksByPagingQuery.java b/src/main/java/com/ftm/server/application/query/FindBookmarksByPagingQuery.java new file mode 100644 index 0000000..19a9b29 --- /dev/null +++ b/src/main/java/com/ftm/server/application/query/FindBookmarksByPagingQuery.java @@ -0,0 +1,21 @@ +package com.ftm.server.application.query; + +import lombok.Getter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +@Getter +public class FindBookmarksByPagingQuery { + + private final Long userId; + private final Pageable pageable; + + private FindBookmarksByPagingQuery(Long userId, int page, int size) { + this.userId = userId; + this.pageable = PageRequest.of(page, size); + } + + public static FindBookmarksByPagingQuery of(Long userId, int page, int size) { + return new FindBookmarksByPagingQuery(userId, page, size); + } +} diff --git a/src/main/java/com/ftm/server/application/service/user/LoadMyBookmarkPostService.java b/src/main/java/com/ftm/server/application/service/user/LoadMyBookmarkPostService.java new file mode 100644 index 0000000..5c78efa --- /dev/null +++ b/src/main/java/com/ftm/server/application/service/user/LoadMyBookmarkPostService.java @@ -0,0 +1,69 @@ +package com.ftm.server.application.service.user; + +import com.ftm.server.application.port.in.user.LoadMyBookmarkPostUseCase; +import com.ftm.server.application.port.out.persistence.user.LoadBookmarkPort; +import com.ftm.server.application.port.out.persistence.user.LoadPostImageUserDomainPort; +import com.ftm.server.application.port.out.persistence.user.LoadPostUserDomainPort; +import com.ftm.server.application.query.FindBookmarksByPagingQuery; +import com.ftm.server.application.query.FindByIdsQuery; +import com.ftm.server.application.vo.post.PostPagingVo; +import com.ftm.server.application.vo.post.PostSummaryVo; +import com.ftm.server.domain.entity.Bookmark; +import com.ftm.server.domain.entity.Post; +import com.ftm.server.domain.entity.PostImage; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LoadMyBookmarkPostService implements LoadMyBookmarkPostUseCase { + + private final LoadBookmarkPort loadBookmarkPort; + private final LoadPostUserDomainPort loadPostUserDomainPort; + private final LoadPostImageUserDomainPort loadPostImageUserDomainPort; + + @Override + public PostPagingVo execute(FindBookmarksByPagingQuery query) { + Slice bookmarks = loadBookmarkPort.loadBookmarksByUserIdWithPaging(query); + List postIds = bookmarks.getContent().stream().map(Bookmark::getPostId).toList(); + FindByIdsQuery postIdsQuery = FindByIdsQuery.from(postIds); + + // 북마크한 유저픽 게시글 목록 조회 + // 최신순으로 정렬된 북마크 목록의 순서를 보장 -> postId : Post Map 매핑 + List posts = loadPostUserDomainPort.loadPostsByIds(postIdsQuery); + Map postMap = + posts.stream().collect(Collectors.toMap(Post::getId, Function.identity())); + + // 게시글 이미지 목록 조회 (대표 이미지) + List postImages = + loadPostImageUserDomainPort.loadRepresentativeImagesByPostIds(postIdsQuery); + Map postIdToImageMap = + postImages.stream() + .collect( + Collectors.toMap( + PostImage::getPostId, + Function.identity(), + BinaryOperator.minBy( + Comparator.comparing(PostImage::getCreatedAt)))); + + // 최신순으로 정렬된 postIds 를 기준으로 items 생성 + List items = + postIds.stream() + .map( + postId -> { + Post post = postMap.get(postId); + PostImage postImage = postIdToImageMap.get(postId); + return PostSummaryVo.from(post, postImage); + }) + .toList(); + + return PostPagingVo.from(items, bookmarks.hasNext()); + } +} diff --git a/src/test/java/com/ftm/server/user/LoadMyBookmarkPostTest.java b/src/test/java/com/ftm/server/user/LoadMyBookmarkPostTest.java new file mode 100644 index 0000000..cf73484 --- /dev/null +++ b/src/test/java/com/ftm/server/user/LoadMyBookmarkPostTest.java @@ -0,0 +1,217 @@ +package com.ftm.server.user; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +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.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.ftm.server.BaseTest; +import com.ftm.server.adapter.in.web.post.dto.request.SavePostProductRequest; +import com.ftm.server.adapter.in.web.post.dto.request.SavePostRequest; +import com.ftm.server.application.command.post.SavePostCommand; +import com.ftm.server.application.command.user.CreateBookmarkCommand; +import com.ftm.server.application.port.in.post.SavePostUseCase; +import com.ftm.server.application.port.in.user.CreateBookmarkUseCase; +import com.ftm.server.application.port.out.s3.S3ImageDeletePort; +import com.ftm.server.application.port.out.s3.S3PostImageUploadPort; +import com.ftm.server.application.port.out.s3.S3PostProductImageUploadPort; +import com.ftm.server.application.port.out.transcation.AfterRollbackExecutorPort; +import com.ftm.server.application.vo.post.PostInfoVo; +import com.ftm.server.common.response.enums.ErrorResponseCode; +import com.ftm.server.domain.entity.User; +import com.ftm.server.domain.enums.GroomingCategory; +import com.ftm.server.domain.enums.HashTag; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.restdocs.request.ParameterDescriptor; +import org.springframework.restdocs.snippet.Attributes; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +public class LoadMyBookmarkPostTest extends BaseTest { + + @MockitoSpyBean private S3PostImageUploadPort s3PostImageUploadPort; + @MockitoSpyBean private S3PostProductImageUploadPort s3PostProductImageUploadPort; + @MockitoSpyBean private S3ImageDeletePort s3ImageDeletePort; + @MockitoSpyBean private AfterRollbackExecutorPort afterRollbackExecutorPort; + + @Autowired private SavePostUseCase savePostUseCase; + @Autowired private CreateBookmarkUseCase createBookmarkUseCase; + + private final ParameterDescriptor queryParametersForPage = + parameterWithName("page") + .optional() + .description("페이지 번호") + .attributes( + new Attributes.Attribute("constraint", "숫자, 최소 0 이상"), + new Attributes.Attribute("default", "0")); + + private final ParameterDescriptor queryParametersForSize = + parameterWithName("size") + .optional() + .description("페이지당 개수") + .attributes( + new Attributes.Attribute("constraint", "숫자, 최소 1 이상 최대 10 이하"), + new Attributes.Attribute("default", "5")); + + private final List responseFieldLoadMyBookmarkPosts = + List.of( + fieldWithPath("status").type(NUMBER).description("응답 상태"), + fieldWithPath("code").type(STRING).description("상태 코드"), + fieldWithPath("message").type(STRING).description("메시지"), + fieldWithPath("data").type(OBJECT).optional().description("응답 데이터"), + fieldWithPath("data.items[]").type(ARRAY).optional().description("작성한 게시글 목록"), + fieldWithPath("data.items[].id").type(NUMBER).description("게시글 ID"), + fieldWithPath("data.items[].title").type(STRING).description("게시글 제목"), + fieldWithPath("data.items[].createdAt").type(STRING).description("게시글 생성 날짜"), + fieldWithPath("data.items[].updatedAt").type(STRING).description("게시글 수정 날짜"), + fieldWithPath("data.items[].imageUrl") + .type(STRING) + .description("게시글 대표 이미지 URL"), + fieldWithPath("data.hasNext").type(BOOLEAN).description("다음 페이지 데이터 존재여부")); + + private ResultActions getResultActions(MockHttpSession session, int page, int size) + throws Exception { + return mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/users/me/bookmarks") + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size)) + .session(session)); + } + + private RestDocumentationResultHandler getDocument(Integer identifier) { + return document( + "loadMyBookmarkPosts/" + identifier, + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint(), getModifiedHeader()), + responseFields(responseFieldLoadMyBookmarkPosts), + queryParameters(queryParametersForPage, queryParametersForSize), + resource( + ResourceSnippetParameters.builder() + .tag("회원") + .summary("북마크한 유저픽 게시글 목록 조회 api") + .description("북마크한 유저픽 게시글 목록을 조회하는 api 입니다.") + .responseFields(responseFieldLoadMyBookmarkPosts) + .queryParameters(queryParametersForPage, queryParametersForSize) + .build())); + } + + @BeforeEach + void set_up() throws Exception { + User user = createTestUser("test@gmail.com", "test1234!"); + + // s3 실제 호출 대신 mock 대입 + doReturn(List.of()).when(s3PostImageUploadPort).uploadImages(new ArrayList<>(List.of())); + doReturn(List.of()) + .when(s3PostProductImageUploadPort) + .uploadImages(new ArrayList<>(List.of())); + doNothing().when(s3ImageDeletePort).deleteImages(any()); + doNothing().when(afterRollbackExecutorPort).doAfterRollback(any()); + + for (int i = 0; i < 5; i++) { + SavePostRequest postRequest = + new SavePostRequest( + "독도 토너 추천 " + i, + GroomingCategory.BEAUTY, + List.of(HashTag.PERFUME), + "
test
", + List.of(new SavePostProductRequest(-1, "독도 토너", "라운드랩", List.of()))); + PostInfoVo post = + savePostUseCase.execute( + SavePostCommand.from(user.getId(), postRequest, List.of(), List.of())); + + CreateBookmarkCommand createBookmarkCommand = + CreateBookmarkCommand.of(user.getId(), post.getId()); + createBookmarkUseCase.execute(createBookmarkCommand); + } + } + + @Test + @Transactional + void 북마크한_유저픽_게시글_목록_조회_성공() throws Exception { + // given + MockHttpSession session = login("test@gmail.com"); + + // when + ResultActions resultActions = getResultActions(session, 0, 4); + + // then + resultActions.andExpect(status().isOk()).andDo(print()); + + resultActions.andDo(getDocument(1)); + } + + @Test + @Transactional + void 북마크한_유저픽_게시글_목록_조회_실패1() throws Exception { + // when + ResultActions resultActions = + mockMvc.perform( + RestDocumentationRequestBuilders.get("/api/users/me/posts") + .param("page", String.valueOf(0)) + .param("size", String.valueOf(5))); + + // then + resultActions.andExpect( + status().is(ErrorResponseCode.NOT_AUTHENTICATED.getHttpStatus().value())); + + resultActions.andDo(getDocument(2)); + } + + @Test + @Transactional + void 북마크한_유저픽_게시글_목록_조회_실패2() throws Exception { + // given + MockHttpSession session = login("test@gmail.com"); + + // when + ResultActions resultActions = getResultActions(session, -1, 5); + + // then + resultActions.andExpect( + status().is( + ErrorResponseCode.BAD_REQUEST_PAGING_INDEX_RANGE + .getHttpStatus() + .value())); + + resultActions.andDo(getDocument(3)); + } + + @Test + @Transactional + void 북마크한_유저픽_게시글_목록_조회_실패3() throws Exception { + // given + MockHttpSession session = login("test@gmail.com"); + + // when + ResultActions resultActions = getResultActions(session, 0, 100); + + // then + resultActions.andExpect( + status().is( + ErrorResponseCode.BAD_REQUEST_PAGING_SIZE_RANGE + .getHttpStatus() + .value())); + + resultActions.andDo(getDocument(4)); + } +}