diff --git a/src/main/java/io/crops/warmletter/domain/share/controller/SharePostController.java b/src/main/java/io/crops/warmletter/domain/share/controller/SharePostController.java index 258b6c44..3a59c670 100644 --- a/src/main/java/io/crops/warmletter/domain/share/controller/SharePostController.java +++ b/src/main/java/io/crops/warmletter/domain/share/controller/SharePostController.java @@ -1,22 +1,17 @@ package io.crops.warmletter.domain.share.controller; +import io.crops.warmletter.domain.share.dto.response.CursorResponse; import io.crops.warmletter.domain.share.dto.response.SharePostDetailResponse; import io.crops.warmletter.domain.share.dto.response.SharePostResponse; import io.crops.warmletter.domain.share.service.SharePostService; import io.crops.warmletter.global.response.BaseResponse; -import io.crops.warmletter.global.response.PageResponse; -import io.crops.warmletter.global.util.PageableConverter; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; - @RestController @RequestMapping("/api/share-posts") @RequiredArgsConstructor @@ -27,11 +22,12 @@ public class SharePostController { @Operation(summary = "공유 게시글 목록 조회", description = "페이징 처리된 공유 게시글 목록을 조회합니다.") @GetMapping() - public ResponseEntity>> getAllPosts( - @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + public ResponseEntity>> getAllPosts( + @RequestParam(required = false) Long cursorId, + @RequestParam(defaultValue = "10") int size ) { return ResponseEntity.status(HttpStatus.OK) - .body(BaseResponse.of(new PageResponse<>(sharePostService.getAllPosts(PageableConverter.convertToPageable(pageable))), "공유 게시글 조회 성공")); + .body(BaseResponse.of(sharePostService.getAllPosts(cursorId, size), "공유 게시글 조회 성공")); } @Operation(summary = "공유 게시글 상세 조회", description = "특정 ID의 공유 게시글 상세 정보를 조회합니다.") diff --git a/src/main/java/io/crops/warmletter/domain/share/dto/response/CursorResponse.java b/src/main/java/io/crops/warmletter/domain/share/dto/response/CursorResponse.java new file mode 100644 index 00000000..f1ef9167 --- /dev/null +++ b/src/main/java/io/crops/warmletter/domain/share/dto/response/CursorResponse.java @@ -0,0 +1,19 @@ +package io.crops.warmletter.domain.share.dto.response; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class CursorResponse { + private List data; + private Long nextCursor; + private boolean hasNext; + + public CursorResponse(List data, Long nextCursor, boolean hasNext) { + this.data = data; + this.nextCursor = nextCursor; + this.hasNext = hasNext; + } +} + diff --git a/src/main/java/io/crops/warmletter/domain/share/entity/SharePost.java b/src/main/java/io/crops/warmletter/domain/share/entity/SharePost.java index 5492fc0a..d8ffd4a3 100644 --- a/src/main/java/io/crops/warmletter/domain/share/entity/SharePost.java +++ b/src/main/java/io/crops/warmletter/domain/share/entity/SharePost.java @@ -12,7 +12,7 @@ @Getter @Table( indexes = { - @Index(name = "idx_sharepost_active_created", columnList = "isActive,createdAt") + @Index(name = "idx_sharepost_active_id", columnList = "isActive,id") } ) public class SharePost extends BaseEntity { diff --git a/src/main/java/io/crops/warmletter/domain/share/exception/SharePageException.java b/src/main/java/io/crops/warmletter/domain/share/exception/SharePageException.java deleted file mode 100644 index b347fa95..00000000 --- a/src/main/java/io/crops/warmletter/domain/share/exception/SharePageException.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.crops.warmletter.domain.share.exception; - -import io.crops.warmletter.global.error.common.ErrorCode; -import io.crops.warmletter.global.error.exception.BusinessException; - -public class SharePageException extends BusinessException { - public SharePageException() { - super(ErrorCode.INVALID_PAGE_REQUEST); - } -} diff --git a/src/main/java/io/crops/warmletter/domain/share/repository/SharePostRepository.java b/src/main/java/io/crops/warmletter/domain/share/repository/SharePostRepository.java index ea869dc2..b5d99a8b 100644 --- a/src/main/java/io/crops/warmletter/domain/share/repository/SharePostRepository.java +++ b/src/main/java/io/crops/warmletter/domain/share/repository/SharePostRepository.java @@ -1,10 +1,10 @@ package io.crops.warmletter.domain.share.repository; import io.crops.warmletter.domain.share.dto.response.SharePostResponse; import io.crops.warmletter.domain.share.entity.SharePost; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface SharePostRepository extends JpaRepository, CustomSharePostRepository { @@ -15,14 +15,14 @@ public interface SharePostRepository extends JpaRepository, Cust "JOIN ShareProposal proposal ON sp.shareProposalId = proposal.id " + "JOIN Member writer ON proposal.requesterId = writer.id " + "JOIN Member recipient ON proposal.recipientId = recipient.id " + - "WHERE sp.isActive = true ") - Page findAllActiveSharePostsWithZipCodes(Pageable pageable); + "WHERE sp.isActive = true " + + "AND (:cursorId IS NULL OR sp.id < :cursorId) " + + "ORDER BY sp.id DESC " + + "LIMIT :size") + List findAllActiveSharePostsWithZipCodes(@Param("cursorId") Long cursorId, @Param("size") int size); @Query("SELECT sp FROM SharePost sp " + "JOIN ShareProposal proposal ON sp.shareProposalId = proposal.id " + "WHERE sp.id = :sharePostId AND proposal.requesterId = :memberId") Optional findByIdAndRequesterId(Long sharePostId, Long memberId); - - - } diff --git a/src/main/java/io/crops/warmletter/domain/share/service/SharePostService.java b/src/main/java/io/crops/warmletter/domain/share/service/SharePostService.java index 9a711d25..8aa20225 100644 --- a/src/main/java/io/crops/warmletter/domain/share/service/SharePostService.java +++ b/src/main/java/io/crops/warmletter/domain/share/service/SharePostService.java @@ -1,16 +1,14 @@ package io.crops.warmletter.domain.share.service; import io.crops.warmletter.domain.auth.facade.AuthFacade; +import io.crops.warmletter.domain.share.dto.response.CursorResponse; import io.crops.warmletter.domain.share.dto.response.SharePostDetailResponse; import io.crops.warmletter.domain.share.dto.response.SharePostResponse; import io.crops.warmletter.domain.share.entity.SharePost; import io.crops.warmletter.domain.share.exception.ShareAccessException; -import io.crops.warmletter.domain.share.exception.SharePageException; import io.crops.warmletter.domain.share.exception.SharePostNotFoundException; import io.crops.warmletter.domain.share.repository.SharePostRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -24,13 +22,17 @@ public class SharePostService { private final AuthFacade authFacade; @Transactional(readOnly = true) - public Page getAllPosts(Pageable pageable) { + public CursorResponse getAllPosts(Long cursorId, int size) { + List responses = sharePostRepository.findAllActiveSharePostsWithZipCodes(cursorId, size+1); - if (pageable.getPageNumber() < 0) { - throw new SharePageException(); + boolean hasNext = responses.size()>size; + + if (hasNext) { + responses = responses.subList(0,size); } + Long nextCursorId = hasNext && !responses.isEmpty() ? responses.get(responses.size() - 1).getSharePostId() : null; - return sharePostRepository.findAllActiveSharePostsWithZipCodes(pageable); + return new CursorResponse<>(responses, nextCursorId, hasNext); } @Transactional(readOnly = true) diff --git a/src/test/java/io/crops/warmletter/domain/share/controller/SharePostControllerTest.java b/src/test/java/io/crops/warmletter/domain/share/controller/SharePostControllerTest.java index 3ea21f57..96f3916e 100644 --- a/src/test/java/io/crops/warmletter/domain/share/controller/SharePostControllerTest.java +++ b/src/test/java/io/crops/warmletter/domain/share/controller/SharePostControllerTest.java @@ -1,4 +1,5 @@ package io.crops.warmletter.domain.share.controller; +import io.crops.warmletter.domain.share.dto.response.CursorResponse; import io.crops.warmletter.domain.share.dto.response.ShareLetterPostResponse; import io.crops.warmletter.domain.share.dto.response.SharePostDetailResponse; import io.crops.warmletter.domain.share.dto.response.SharePostResponse; @@ -13,7 +14,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.data.domain.*; import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @@ -21,7 +21,6 @@ import java.util.Collections; import java.util.List; import static org.hamcrest.Matchers.hasSize; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -43,73 +42,78 @@ class SharePostControllerTest { @BeforeEach void createSharePost() { - // 테스트에서 사용되는 값을 고정 LocalDateTime fixedCreatedAt = LocalDateTime.of(2025, 2, 28, 12, 0, 0, 0); - // SharePostResponse 객체를 새 생성자를 사용하여 생성 sharePostResponse1 = new SharePostResponse(1L, 1L, "12345", "67890", "to share my post", true, fixedCreatedAt); sharePostResponse2 = new SharePostResponse(2L, 2L, "12345", "67890", "to share my post1", true, fixedCreatedAt); } @Test - @DisplayName("페이징된 공유 게시글 반환 ") + @DisplayName("커서 공유 게시글 반환 ") void getAllPosts() throws Exception { // given - Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.ASC, "createdAt")); List posts = List.of(sharePostResponse1, sharePostResponse2); - Page postPage = new PageImpl<>(posts, pageable, posts.size()); - when(sharePostService.getAllPosts(any(Pageable.class))).thenReturn(postPage); + CursorResponse cursorResponse = new CursorResponse<>(posts, 2L, true); + when(sharePostService.getAllPosts(null, 10)).thenReturn(cursorResponse); // when mockMvc.perform(get("/api/share-posts") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.content", hasSize(2))) - .andExpect(jsonPath("$.data.content[0].content").value("to share my post")) - .andExpect(jsonPath("$.data.content[1].content").value("to share my post1")) - .andExpect(jsonPath("$.data.currentPage").value(1)) - .andExpect(jsonPath("$.data.totalElements").value(2)) - .andExpect(jsonPath("$.data.size").value(10)) + .andExpect(jsonPath("$.data.data", hasSize(2))) + .andExpect(jsonPath("$.data.data[0].content").value("to share my post")) + .andExpect(jsonPath("$.data.data[1].content").value("to share my post1")) + .andExpect(jsonPath("$.data.nextCursor").value(2)) + .andExpect(jsonPath("$.data.hasNext").value(true)) .andExpect(jsonPath("$.message").value("공유 게시글 조회 성공")) .andDo(print()); } + + @Test - @DisplayName("페이지 파라미터에 따라서 해당 페이지 반환 ") + @DisplayName("커서 ID 파라미터에 따라서 해당 페이지 반환 ") void getAllPosts_ReturnsSpecificPage() throws Exception { // given - Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt")); - Page emptyPage = new PageImpl<>(Collections.emptyList(), pageable, 20); + List posts = List.of(sharePostResponse2); + Long cursorId = 3L; + Long nextCursorId = 2L; + CursorResponse cursorResponse = new CursorResponse<>(posts, nextCursorId, true); - when(sharePostService.getAllPosts(any(Pageable.class))).thenReturn(emptyPage); + when(sharePostService.getAllPosts(cursorId,10)).thenReturn(cursorResponse); // when & then mockMvc.perform(get("/api/share-posts") - .param("page", "1") + .param("cursorId", cursorId.toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.content").isArray()) - .andExpect(jsonPath("$.data.currentPage").value(1)) - .andExpect(jsonPath("$.data.totalElements").value(20)) + .andExpect(jsonPath("$.data.data").isArray()) + .andExpect(jsonPath("$.data.data", hasSize(1))) + .andExpect(jsonPath("$.data.nextCursor").value(nextCursorId)) + .andExpect(jsonPath("$.data.hasNext").value(true)) .andExpect(jsonPath("$.message").value("공유 게시글 조회 성공")) .andDo(print()); } - - @Test - @DisplayName("음수 페이지 요청시 예외 발생") - void getAllPosts_ThrowsException_WhenPageNumberIsNegative() throws Exception { + @DisplayName("마지막 페이지 조회 - 다음 페이지 없음") + void getAllPosts_LastPage() throws Exception { // given - when(sharePostService.getAllPosts(any(Pageable.class))) - .thenThrow(new BusinessException(ErrorCode.INVALID_PAGE_REQUEST)); + List posts = List.of(sharePostResponse2); + Long cursorId = 3L; + CursorResponse cursorResponse = new CursorResponse<>(posts, null, false); + + when(sharePostService.getAllPosts(cursorId, 10)).thenReturn(cursorResponse); // when & then mockMvc.perform(get("/api/share-posts") - .param("page", "-1") + .param("cursorId", cursorId.toString()) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.code").value(ErrorCode.INVALID_PAGE_REQUEST.getCode())) - .andExpect(jsonPath("$.message").value(ErrorCode.INVALID_PAGE_REQUEST.getMessage())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.data").isArray()) + .andExpect(jsonPath("$.data.data", hasSize(1))) + .andExpect(jsonPath("$.data.nextCursor").isEmpty()) + .andExpect(jsonPath("$.data.hasNext").value(false)) + .andExpect(jsonPath("$.message").value("공유 게시글 조회 성공")) .andDo(print()); } diff --git a/src/test/java/io/crops/warmletter/domain/share/service/SharePostServiceTest.java b/src/test/java/io/crops/warmletter/domain/share/service/SharePostServiceTest.java index 79fe1cb3..a1173500 100644 --- a/src/test/java/io/crops/warmletter/domain/share/service/SharePostServiceTest.java +++ b/src/test/java/io/crops/warmletter/domain/share/service/SharePostServiceTest.java @@ -1,11 +1,11 @@ package io.crops.warmletter.domain.share.service; import io.crops.warmletter.domain.auth.facade.AuthFacade; +import io.crops.warmletter.domain.share.dto.response.CursorResponse; import io.crops.warmletter.domain.share.dto.response.ShareLetterPostResponse; import io.crops.warmletter.domain.share.dto.response.SharePostDetailResponse; import io.crops.warmletter.domain.share.dto.response.SharePostResponse; import io.crops.warmletter.domain.share.entity.SharePost; import io.crops.warmletter.domain.share.exception.ShareAccessException; -import io.crops.warmletter.domain.share.exception.SharePageException; import io.crops.warmletter.domain.share.repository.SharePostRepository; import io.crops.warmletter.global.error.common.ErrorCode; import io.crops.warmletter.global.error.exception.BusinessException; @@ -16,7 +16,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.*; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; @@ -30,8 +29,6 @@ class SharePostServiceTest { @Mock private SharePostRepository sharePostRepository; - @Mock - private Pageable pageable; @Mock AuthFacade authFacade; @@ -51,114 +48,114 @@ void setUp() { } @Test - @DisplayName("여러 페이지가 있는 경우 마지막 페이지 조회 성공") - void getAllPosts_ReturnsLastPage() { + @DisplayName("커서 기반 페이징: 첫 페이지 조회 성공") + void getAllPosts_ReturnsFirstPage() { // given - Pageable lastPageable = PageRequest.of(2, 5, Sort.by(Sort.Direction.DESC, "createdAt")); + Long cursorId = null; + int size = 10; List responses = List.of( - new SharePostResponse(9L, 5L, "55555", "66666", "마지막 페이지 게시글", true, LocalDateTime.now()) + new SharePostResponse(1L, 1L, "12345", "67890", "첫 번째 게시글", true, LocalDateTime.now()), + new SharePostResponse(2L, 2L, "13579", "24680", "두 번째 게시글", true, LocalDateTime.now().minusDays(1)) ); - // 총 11개 게시글 중 마지막 페이지(3페이지, 인덱스 2)에 1개의 게시글이 있는 상황 - Page responsePage = new PageImpl<>(responses, lastPageable, 11); - - when(sharePostRepository.findAllActiveSharePostsWithZipCodes(lastPageable)).thenReturn(responsePage); + when(sharePostRepository.findAllActiveSharePostsWithZipCodes(cursorId, size + 1)).thenReturn(responses); // when - Page result = sharePostService.getAllPosts(lastPageable); + CursorResponse result = sharePostService.getAllPosts(cursorId, size); // then assertAll( - () -> assertThat(result.getContent()).hasSize(1), - () -> assertThat(result.getContent().get(0).getContent()).isEqualTo("마지막 페이지 게시글"), - () -> assertThat(result.getTotalElements()).isEqualTo(11), - () -> assertThat(result.getNumber()).isEqualTo(2), - () -> assertThat(result.getTotalPages()).isEqualTo(3) + () -> assertThat(result.getData()).hasSize(2), + () -> assertThat(result.getData().get(0).getContent()).isEqualTo("첫 번째 게시글"), + () -> assertThat(result.getData().get(1).getContent()).isEqualTo("두 번째 게시글"), + () -> assertThat(result.getNextCursor()).isNull(), + () -> assertThat(result.isHasNext()).isFalse() ); - verify(sharePostRepository).findAllActiveSharePostsWithZipCodes(lastPageable); + verify(sharePostRepository).findAllActiveSharePostsWithZipCodes(cursorId, size + 1); } @Test - @DisplayName("특정 페이지가 비어있는 경우 빈 페이지 반환") - void getAllPosts_ReturnsEmptyPage_WhenSpecificPageIsEmpty() { + @DisplayName("커서 기반 페이징: 다음 페이지가 있는 경우") + void getAllPosts_WithNextPage() { // given - // 존재하지 않는 페이지 번호로 요청 - Pageable emptyPageable = PageRequest.of(5, 10, Sort.by(Sort.Direction.DESC, "createdAt")); + Long cursorId = null; + int size = 2; - // 총 게시글은 20개이지만 요청한 페이지는 범위를 벗어남 - Page emptyPage = new PageImpl<>(Collections.emptyList(), emptyPageable, 20); + List responses = List.of( + new SharePostResponse(3L, 3L, "12345", "67890", "첫 번째 게시글", true, LocalDateTime.now()), + new SharePostResponse(2L, 2L, "13579", "24680", "두 번째 게시글", true, LocalDateTime.now().minusDays(1)), + new SharePostResponse(1L, 1L, "11111", "22222", "세 번째 게시글", true, LocalDateTime.now().minusDays(2)) + ); - when(sharePostRepository.findAllActiveSharePostsWithZipCodes(emptyPageable)).thenReturn(emptyPage); + when(sharePostRepository.findAllActiveSharePostsWithZipCodes(cursorId, size + 1)).thenReturn(responses); // when - Page result = sharePostService.getAllPosts(emptyPageable); + CursorResponse result = sharePostService.getAllPosts(cursorId, size); // then assertAll( - () -> assertThat(result.getContent()).isEmpty(), - () -> assertThat(result.getTotalElements()).isEqualTo(20), - () -> assertThat(result.getNumber()).isEqualTo(5), - () -> assertThat(result.getTotalPages()).isEqualTo(2) // 10개씩 페이징하면 총 2페이지 + () -> assertThat(result.getData()).hasSize(2), + () -> assertThat(result.getData().get(0).getContent()).isEqualTo("첫 번째 게시글"), + () -> assertThat(result.getData().get(1).getContent()).isEqualTo("두 번째 게시글"), + () -> assertThat(result.getNextCursor()).isEqualTo(2L), + () -> assertThat(result.isHasNext()).isTrue() ); - verify(sharePostRepository).findAllActiveSharePostsWithZipCodes(emptyPageable); + verify(sharePostRepository).findAllActiveSharePostsWithZipCodes(cursorId, size + 1); } @Test - @DisplayName("활성화된 게시글이 없을 경우 빈 페이지 반환") - void getAllPosts_ReturnsEmptyPage_WhenNoActivePost() { + @DisplayName("커서 기반 페이징: 마지막 페이지 조회 성공") + void getAllPosts_LastPage() { // given - Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt")); - Page emptyPage = new PageImpl<>(Collections.emptyList(), pageable, 0); - when(sharePostRepository.findAllActiveSharePostsWithZipCodes(pageable)).thenReturn(emptyPage); + Long cursorId = 3L; + int size = 2; + + // 마지막 페이지이므로 size보다 적은 항목만 반환 + List responses = List.of( + new SharePostResponse(2L, 2L, "12345", "67890", "마지막 페이지 첫 번째 게시글", true, LocalDateTime.now()), + new SharePostResponse(1L, 1L, "13579", "24680", "마지막 페이지 두 번째 게시글", true, LocalDateTime.now().minusDays(1)) + ); + + when(sharePostRepository.findAllActiveSharePostsWithZipCodes(cursorId, size + 1)).thenReturn(responses); // when - Page result = sharePostService.getAllPosts(pageable); + CursorResponse result = sharePostService.getAllPosts(cursorId, size); // then assertAll( - () -> assertThat(result.getContent()).isEmpty(), - () -> assertThat(result.getTotalElements()).isZero(), - () -> assertThat(result.getTotalPages()).isZero() + () -> assertThat(result.getData()).hasSize(2), + () -> assertThat(result.getData().get(0).getContent()).isEqualTo("마지막 페이지 첫 번째 게시글"), + () -> assertThat(result.getData().get(1).getContent()).isEqualTo("마지막 페이지 두 번째 게시글"), + () -> assertThat(result.getNextCursor()).isNull(), + () -> assertThat(result.isHasNext()).isFalse() ); - verify(sharePostRepository).findAllActiveSharePostsWithZipCodes(pageable); + verify(sharePostRepository).findAllActiveSharePostsWithZipCodes(cursorId, size + 1); } @Test - @DisplayName("활성화된 게시글 목록 페이징 조회 성공") - void getAllPosts_ReturnsActivePosts() { + @DisplayName("커서 기반 페이징: 데이터가 없는 경우") + void getAllPosts_EmptyData() { // given - Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt")); + Long cursorId = null; + int size = 10; - List responses = List.of( - new SharePostResponse(1L, 1L, "12345", "67890", "to share my post", true, LocalDateTime.now()), - new SharePostResponse(2L, 2L, "13579", "24680", "to share my post1", true, LocalDateTime.now().minusDays(1)) - ); - - Page responsePage = new PageImpl<>(responses, pageable, responses.size()); - - when(sharePostRepository.findAllActiveSharePostsWithZipCodes(pageable)).thenReturn(responsePage); + when(sharePostRepository.findAllActiveSharePostsWithZipCodes(cursorId, size + 1)).thenReturn(Collections.emptyList()); // when - Page result = sharePostService.getAllPosts(pageable); + CursorResponse result = sharePostService.getAllPosts(cursorId, size); // then assertAll( - () -> assertThat(result.getContent()).hasSize(2), - () -> assertThat(result.getContent().get(0).getContent()).isEqualTo("to share my post"), - () -> assertThat(result.getContent().get(1).getContent()).isEqualTo("to share my post1"), - () -> assertThat(result.getContent().get(0).getWriterZipCode()).isEqualTo("12345"), - () -> assertThat(result.getContent().get(0).getReceiverZipCode()).isEqualTo("67890"), - () -> assertThat(result.getContent().get(1).getWriterZipCode()).isEqualTo("13579"), - () -> assertThat(result.getContent().get(1).getReceiverZipCode()).isEqualTo("24680"), - () -> assertThat(result.getTotalElements()).isEqualTo(2), - () -> assertThat(result.getNumber()).isZero() + () -> assertThat(result.getData()).isEmpty(), + () -> assertThat(result.getNextCursor()).isNull(), + () -> assertThat(result.isHasNext()).isFalse() ); - verify(sharePostRepository).findAllActiveSharePostsWithZipCodes(pageable); + verify(sharePostRepository).findAllActiveSharePostsWithZipCodes(cursorId, size + 1); } @DisplayName("게시글 상세 조회 성공") @@ -267,20 +264,6 @@ void getPostDetail_NotFound() { verify(sharePostRepository).findDetailById(sharePostId); } - @Test - @DisplayName("음수 페이지 요청시 예외 발생") - void getAllPosts_ThrowsException_WhenPageNumberIsNegative() { - // given - Pageable mockPageable = mock(Pageable.class); - when(mockPageable.getPageNumber()).thenReturn(-1); - - // when & then - SharePageException exception = assertThrows(SharePageException.class, - () -> sharePostService.getAllPosts(mockPageable)); - - // repository는 호출되지 않아야 함 - verify(sharePostRepository, never()).findAllActiveSharePostsWithZipCodes(any()); - } @Test @DisplayName("내가 요청한 활성화된 공유 게시글 조회 성공")