From 36f8c5118778144c33ac134d3e344548d5a04ea8 Mon Sep 17 00:00:00 2001 From: chaewss Date: Sat, 11 Nov 2023 18:54:59 +0900 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20promotion=20schema=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20#105?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/schema.sql | 7 +++++- src/main/java/com/cvsgo/entity/Promotion.java | 24 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/sql/schema.sql b/sql/schema.sql index c9f392fd..2f3f0e19 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -106,9 +106,14 @@ create table product_like ( create table promotion ( id bigint not null auto_increment, + name varchar(50) not null unique, + image_url varchar(255) not null, + landing_url varchar(255), + priority integer, + start_at datetime, + end_at datetime, created_at datetime, modified_at datetime, - image_url varchar(255), primary key (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/src/main/java/com/cvsgo/entity/Promotion.java b/src/main/java/com/cvsgo/entity/Promotion.java index ac274096..cccc0aba 100644 --- a/src/main/java/com/cvsgo/entity/Promotion.java +++ b/src/main/java/com/cvsgo/entity/Promotion.java @@ -1,9 +1,12 @@ package com.cvsgo.entity; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -18,11 +21,30 @@ public class Promotion extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @NotNull + @Column(unique = true) + private String name; + + @NotNull private String imageUrl; + private String landingUrl; + + private Integer priority; + + private LocalDateTime startAt; + + private LocalDateTime endAt; + @Builder - public Promotion(Long id, String imageUrl) { + public Promotion(Long id, String name, String imageUrl, String landingUrl, Integer priority, + LocalDateTime startAt, LocalDateTime endAt) { this.id = id; + this.name = name; this.imageUrl = imageUrl; + this.landingUrl = landingUrl; + this.priority = priority; + this.startAt = startAt; + this.endAt = endAt; } } From 5c287f5b733952880426f86079cfc9266a9de2dc Mon Sep 17 00:00:00 2001 From: chaewss Date: Sat, 11 Nov 2023 18:56:08 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EB=AA=A8?= =?UTF-8?q?=EC=85=98=20=EC=A1=B0=ED=9A=8C=20API=20=EC=B6=94=EA=B0=80=20#10?= =?UTF-8?q?5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/cvsgo/config/WebConfig.java | 2 +- .../cvsgo/controller/PromotionController.java | 25 ++++++++++++++++ .../promotion/ReadPromotionResponseDto.java | 24 +++++++++++++++ .../cvsgo/repository/PromotionRepository.java | 8 +++++ .../com/cvsgo/service/PromotionService.java | 29 +++++++++++++++++++ 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/cvsgo/controller/PromotionController.java create mode 100644 src/main/java/com/cvsgo/dto/promotion/ReadPromotionResponseDto.java create mode 100644 src/main/java/com/cvsgo/service/PromotionService.java diff --git a/src/main/java/com/cvsgo/config/WebConfig.java b/src/main/java/com/cvsgo/config/WebConfig.java index 3c87ba8d..a213323b 100644 --- a/src/main/java/com/cvsgo/config/WebConfig.java +++ b/src/main/java/com/cvsgo/config/WebConfig.java @@ -30,6 +30,6 @@ public void addInterceptors(InterceptorRegistry registry) { .addPathPatterns("/**") .excludePathPatterns("/", "/docs/**", "/*.ico", "/api/auth/login", "/api/users", "/api/tags", "/api/users/emails/*/exists", "/api/users/nicknames/*/exists", - "/api/products", "/api/products/*", "/api/products/*/tags"); + "/api/promotions", "/api/products", "/api/products/*", "/api/products/*/tags"); } } diff --git a/src/main/java/com/cvsgo/controller/PromotionController.java b/src/main/java/com/cvsgo/controller/PromotionController.java new file mode 100644 index 00000000..9579f411 --- /dev/null +++ b/src/main/java/com/cvsgo/controller/PromotionController.java @@ -0,0 +1,25 @@ +package com.cvsgo.controller; + +import com.cvsgo.dto.SuccessResponse; +import com.cvsgo.dto.promotion.ReadPromotionResponseDto; +import com.cvsgo.service.PromotionService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/promotions") +public class PromotionController { + + private final PromotionService promotionService; + + @GetMapping + private SuccessResponse> readPromotionList(Pageable pageable) { + return SuccessResponse.from(promotionService.readPromotionList(pageable)); + } + +} diff --git a/src/main/java/com/cvsgo/dto/promotion/ReadPromotionResponseDto.java b/src/main/java/com/cvsgo/dto/promotion/ReadPromotionResponseDto.java new file mode 100644 index 00000000..5baf99db --- /dev/null +++ b/src/main/java/com/cvsgo/dto/promotion/ReadPromotionResponseDto.java @@ -0,0 +1,24 @@ +package com.cvsgo.dto.promotion; + +import com.cvsgo.entity.Promotion; +import lombok.Getter; + +@Getter +public class ReadPromotionResponseDto { + + private final Long id; + + private final String imageUrl; + + private final String landingUrl; + + public ReadPromotionResponseDto(Promotion promotion) { + this.id = promotion.getId(); + this.imageUrl = promotion.getImageUrl(); + this.landingUrl = promotion.getLandingUrl(); + } + + public static ReadPromotionResponseDto from(Promotion promotion) { + return new ReadPromotionResponseDto(promotion); + } +} diff --git a/src/main/java/com/cvsgo/repository/PromotionRepository.java b/src/main/java/com/cvsgo/repository/PromotionRepository.java index 32c9caae..9aaf62b9 100644 --- a/src/main/java/com/cvsgo/repository/PromotionRepository.java +++ b/src/main/java/com/cvsgo/repository/PromotionRepository.java @@ -1,8 +1,16 @@ package com.cvsgo.repository; import com.cvsgo.entity.Promotion; +import java.time.LocalDateTime; +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; public interface PromotionRepository extends JpaRepository { + @Query(value = "select p from Promotion p where p.startAt <= :now and p.endAt > :now ORDER BY p.priority") + Page findActivePromotions(@Param("now") LocalDateTime now, Pageable pageable); + } diff --git a/src/main/java/com/cvsgo/service/PromotionService.java b/src/main/java/com/cvsgo/service/PromotionService.java new file mode 100644 index 00000000..4e945f5f --- /dev/null +++ b/src/main/java/com/cvsgo/service/PromotionService.java @@ -0,0 +1,29 @@ +package com.cvsgo.service; + +import com.cvsgo.dto.promotion.ReadPromotionResponseDto; +import com.cvsgo.repository.PromotionRepository; +import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PromotionService { + + private final PromotionRepository promotionRepository; + + /** + * 현재 로컬 날짜 및 시간 기준 활성된 프로모션 목록을 우선순위 순으로 조회한다. + * + * @return 프로모션 목록 + */ + @Transactional(readOnly = true) + public Page readPromotionList(Pageable pageable) { + return promotionRepository.findActivePromotions(LocalDateTime.now(), pageable) + .map(ReadPromotionResponseDto::from); + } + +} From 1604ab90e18c00847c8e783d3df7d4647df828fa Mon Sep 17 00:00:00 2001 From: chaewss Date: Sat, 11 Nov 2023 18:56:41 +0900 Subject: [PATCH 3/5] =?UTF-8?q?test:=20=ED=94=84=EB=A1=9C=EB=AA=A8?= =?UTF-8?q?=EC=85=98=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20#105?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PromotionControllerTest.java | 99 +++++++++++++++++++ .../repository/PromotionRepositoryTest.java | 58 +++++++++++ .../cvsgo/service/PromotionServiceTest.java | 60 +++++++++++ 3 files changed, 217 insertions(+) create mode 100644 src/test/java/com/cvsgo/controller/PromotionControllerTest.java create mode 100644 src/test/java/com/cvsgo/repository/PromotionRepositoryTest.java create mode 100644 src/test/java/com/cvsgo/service/PromotionServiceTest.java diff --git a/src/test/java/com/cvsgo/controller/PromotionControllerTest.java b/src/test/java/com/cvsgo/controller/PromotionControllerTest.java new file mode 100644 index 00000000..e6380410 --- /dev/null +++ b/src/test/java/com/cvsgo/controller/PromotionControllerTest.java @@ -0,0 +1,99 @@ +package com.cvsgo.controller; + +import static com.cvsgo.ApiDocumentUtils.documentIdentifier; +import static com.cvsgo.ApiDocumentUtils.getDocumentRequest; +import static com.cvsgo.ApiDocumentUtils.getDocumentResponse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.relaxedResponseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.SharedHttpSessionConfigurer.sharedHttpSession; + +import com.cvsgo.argumentresolver.LoginUserArgumentResolver; +import com.cvsgo.config.WebConfig; +import com.cvsgo.dto.promotion.ReadPromotionResponseDto; +import com.cvsgo.entity.Promotion; +import com.cvsgo.interceptor.AuthInterceptor; +import com.cvsgo.service.PromotionService; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageImpl; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +@ExtendWith(RestDocumentationExtension.class) +@WebMvcTest(PromotionController.class) +class PromotionControllerTest { + + @MockBean + LoginUserArgumentResolver loginUserArgumentResolver; + + @MockBean + WebConfig webConfig; + + @MockBean + AuthInterceptor authInterceptor; + + @MockBean + private PromotionService promotionService; + + private MockMvc mockMvc; + + @BeforeEach + void setup(WebApplicationContext webApplicationContext, + RestDocumentationContextProvider restDocumentation) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) + .apply(documentationConfiguration(restDocumentation)) + .apply(sharedHttpSession()) + .addFilters(new CharacterEncodingFilter("UTF-8", true)) + .build(); + } + + @Test + @DisplayName("프로모션 목록을 정상적으로 조회하면 HTTP 200을 응답한다") + void respond_200_when_read_promotion_list_successfully() throws Exception { + List responseDto = List.of(new ReadPromotionResponseDto(promotion1), new ReadPromotionResponseDto(promotion2)); + given(promotionService.readPromotionList(any())).willReturn(new PageImpl<>(responseDto)); + + mockMvc.perform(get("/api/promotions").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()) + .andDo(document(documentIdentifier, + getDocumentRequest(), + getDocumentResponse(), + relaxedResponseFields( + fieldWithPath("data.content[].id").type(JsonFieldType.NUMBER).description("프로모션 ID"), + fieldWithPath("data.content[].imageUrl").type(JsonFieldType.STRING).description("프로모션 이미지 url"), + fieldWithPath("data.content[].landingUrl").type(JsonFieldType.STRING).description("프로모션 랜딩 url") + ) + )); + } + + Promotion promotion1 = Promotion.builder() + .id(1L) + .imageUrl("imageUrl1") + .landingUrl("landindUrl1") + .build(); + + Promotion promotion2 = Promotion.builder() + .id(2L) + .imageUrl("imageUrl2") + .landingUrl("landindUrl2") + .build(); +} diff --git a/src/test/java/com/cvsgo/repository/PromotionRepositoryTest.java b/src/test/java/com/cvsgo/repository/PromotionRepositoryTest.java new file mode 100644 index 00000000..e96693d6 --- /dev/null +++ b/src/test/java/com/cvsgo/repository/PromotionRepositoryTest.java @@ -0,0 +1,58 @@ +package com.cvsgo.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.cvsgo.config.TestConfig; +import com.cvsgo.entity.Promotion; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +@Import(TestConfig.class) +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class PromotionRepositoryTest { + + @Autowired + PromotionRepository promotionRepository; + + @BeforeEach + void initData() { + promotion1 = Promotion.builder().id(1L).name("프로모션1").imageUrl("imageUrl1").landingUrl("landindUrl1") + .priority(3).startAt(LocalDateTime.now().minusDays(3)).endAt(LocalDateTime.now().minusDays(1)).build(); + promotion2 = Promotion.builder().id(2L).name("프로모션2").imageUrl("imageUrl2").landingUrl("landindUrl2") + .priority(2).startAt(LocalDateTime.now().minusDays(1)).endAt(LocalDateTime.now().plusDays(1)).build(); + promotion3 = Promotion.builder().id(3L).name("프로모션3").imageUrl("imageUrl3").landingUrl("landindUrl3") + .priority(1).startAt(LocalDateTime.now().minusDays(5)).endAt(LocalDateTime.now().plusDays(6)).build(); + promotion4 = Promotion.builder().id(2L).name("프로모션4").imageUrl("imageUrl2").landingUrl("landindUrl2") + .priority(1).startAt(LocalDateTime.now().plusDays(1)).endAt(LocalDateTime.now().plusDays(3)).build(); + promotionRepository.saveAll(List.of(promotion1, promotion2, promotion3, promotion4)); + } + + @Test + @DisplayName("활성된 프로모션을 조회한다") + void find_active_promotions() { + // when + Page foundPromotions = promotionRepository.findActivePromotions( + LocalDateTime.now(), PageRequest.of(0, 20)); + + // then + assertThat(foundPromotions).isNotEmpty(); + assertThat(foundPromotions.getContent()).hasSize(2); + assertThat(foundPromotions.getContent().get(0).getPriority()).isEqualTo(1); + assertThat(foundPromotions.getContent().get(1).getPriority()).isEqualTo(2); + } + + private Promotion promotion1; + private Promotion promotion2; + private Promotion promotion3; + private Promotion promotion4; +} diff --git a/src/test/java/com/cvsgo/service/PromotionServiceTest.java b/src/test/java/com/cvsgo/service/PromotionServiceTest.java new file mode 100644 index 00000000..12eebe84 --- /dev/null +++ b/src/test/java/com/cvsgo/service/PromotionServiceTest.java @@ -0,0 +1,60 @@ +package com.cvsgo.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; + +import com.cvsgo.dto.promotion.ReadPromotionResponseDto; +import com.cvsgo.entity.Promotion; +import com.cvsgo.repository.PromotionRepository; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +@ExtendWith(MockitoExtension.class) +class PromotionServiceTest { + + @Mock + private PromotionRepository promotionRepository; + + @InjectMocks + PromotionService promotionService; + + @Test + @DisplayName("프로모션 목록을 정상적으로 조회한다") + void succeed_to_read_promotion_list() { + Pageable pageable = PageRequest.of(0, 20); + given(promotionRepository.findActivePromotions(any(), any())).willReturn(getPromotionList()); + + Page result = promotionService.readPromotionList(pageable); + + assertEquals(getPromotionList().getTotalElements(), result.getTotalElements()); + then(promotionRepository).should(times(1)).findActivePromotions(any(), any()); + } + + Promotion promotion1 = Promotion.builder() + .id(1L) + .imageUrl("imageUrl1") + .landingUrl("landindUrl1") + .build(); + + Promotion promotion2 = Promotion.builder() + .id(2L) + .imageUrl("imageUrl2") + .landingUrl("landindUrl2") + .build(); + + private Page getPromotionList() { + return new PageImpl<>(List.of(promotion1, promotion2)); + } +} From 7903aec672bb7113747700e9f9d06e9944d94cc8 Mon Sep 17 00:00:00 2001 From: chaewss Date: Sat, 11 Nov 2023 18:57:02 +0900 Subject: [PATCH 4/5] =?UTF-8?q?docs:=20=ED=94=84=EB=A1=9C=EB=AA=A8?= =?UTF-8?q?=EC=85=98=20=EC=A1=B0=ED=9A=8C=20=EB=AC=B8=EC=84=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20#105?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/api-doc.adoc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/docs/asciidoc/api-doc.adoc b/src/docs/asciidoc/api-doc.adoc index 7af03a72..01f9a5fa 100644 --- a/src/docs/asciidoc/api-doc.adoc +++ b/src/docs/asciidoc/api-doc.adoc @@ -426,3 +426,12 @@ include::{snippets}/notice-controller-test/respond_200_when_read_notice_successf | `404 NOT FOUND` | `NOT_FOUND_NOTICE` | 해당하는 공지사항이 없는 경우 |=== + +== 7. 프로모션 +=== 7-1. 프로모션 목록 조회 +==== Sample Request +include::{snippets}/promotion-controller-test/respond_200_when_read_promotion_list_successfully/http-request.adoc[] +==== Response Fields +include::{snippets}/promotion-controller-test/respond_200_when_read_promotion_list_successfully/response-fields.adoc[] +==== Sample Response +include::{snippets}/promotion-controller-test/respond_200_when_read_promotion_list_successfully/http-response.adoc[] From 6e9c1052ad8a343bb7cf374a50ddb711b59fdf1f Mon Sep 17 00:00:00 2001 From: chaewss Date: Fri, 12 Jan 2024 01:15:40 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20JPQL=EC=9D=84=20QueryDSL?= =?UTF-8?q?=EB=A1=9C=20=EB=8C=80=EC=B2=B4=20#105?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cvsgo/controller/PromotionController.java | 7 +++--- .../repository/PromotionCustomRepository.java | 11 ++++++++ .../PromotionCustomRepositoryImpl.java | 25 +++++++++++++++++++ .../cvsgo/repository/PromotionRepository.java | 5 +--- .../com/cvsgo/service/PromotionService.java | 9 ++++--- .../controller/PromotionControllerTest.java | 10 +++----- .../repository/PromotionRepositoryTest.java | 13 ++++------ .../cvsgo/service/PromotionServiceTest.java | 19 ++++++-------- 8 files changed, 62 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/cvsgo/repository/PromotionCustomRepository.java create mode 100644 src/main/java/com/cvsgo/repository/PromotionCustomRepositoryImpl.java diff --git a/src/main/java/com/cvsgo/controller/PromotionController.java b/src/main/java/com/cvsgo/controller/PromotionController.java index 9579f411..b032a7a6 100644 --- a/src/main/java/com/cvsgo/controller/PromotionController.java +++ b/src/main/java/com/cvsgo/controller/PromotionController.java @@ -3,9 +3,8 @@ import com.cvsgo.dto.SuccessResponse; import com.cvsgo.dto.promotion.ReadPromotionResponseDto; import com.cvsgo.service.PromotionService; +import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -18,8 +17,8 @@ public class PromotionController { private final PromotionService promotionService; @GetMapping - private SuccessResponse> readPromotionList(Pageable pageable) { - return SuccessResponse.from(promotionService.readPromotionList(pageable)); + private SuccessResponse> readPromotionList() { + return SuccessResponse.from(promotionService.readPromotionList()); } } diff --git a/src/main/java/com/cvsgo/repository/PromotionCustomRepository.java b/src/main/java/com/cvsgo/repository/PromotionCustomRepository.java new file mode 100644 index 00000000..0be29989 --- /dev/null +++ b/src/main/java/com/cvsgo/repository/PromotionCustomRepository.java @@ -0,0 +1,11 @@ +package com.cvsgo.repository; + +import com.cvsgo.entity.Promotion; +import java.time.LocalDateTime; +import java.util.List; + +public interface PromotionCustomRepository { + + List findActivePromotions(LocalDateTime now); + +} diff --git a/src/main/java/com/cvsgo/repository/PromotionCustomRepositoryImpl.java b/src/main/java/com/cvsgo/repository/PromotionCustomRepositoryImpl.java new file mode 100644 index 00000000..52251177 --- /dev/null +++ b/src/main/java/com/cvsgo/repository/PromotionCustomRepositoryImpl.java @@ -0,0 +1,25 @@ +package com.cvsgo.repository; + +import static com.cvsgo.entity.QPromotion.promotion; + +import com.cvsgo.entity.Promotion; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class PromotionCustomRepositoryImpl implements PromotionCustomRepository { + + private final JPAQueryFactory queryFactory; + + public List findActivePromotions(LocalDateTime now) { + return queryFactory.selectFrom(promotion) + .where(promotion.startAt.loe(now).and(promotion.endAt.goe(now))) + .orderBy(promotion.priority.asc()) + .fetch(); + } + +} diff --git a/src/main/java/com/cvsgo/repository/PromotionRepository.java b/src/main/java/com/cvsgo/repository/PromotionRepository.java index 9aaf62b9..efb852de 100644 --- a/src/main/java/com/cvsgo/repository/PromotionRepository.java +++ b/src/main/java/com/cvsgo/repository/PromotionRepository.java @@ -8,9 +8,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface PromotionRepository extends JpaRepository { - - @Query(value = "select p from Promotion p where p.startAt <= :now and p.endAt > :now ORDER BY p.priority") - Page findActivePromotions(@Param("now") LocalDateTime now, Pageable pageable); +public interface PromotionRepository extends JpaRepository, PromotionCustomRepository { } diff --git a/src/main/java/com/cvsgo/service/PromotionService.java b/src/main/java/com/cvsgo/service/PromotionService.java index 4e945f5f..41297e84 100644 --- a/src/main/java/com/cvsgo/service/PromotionService.java +++ b/src/main/java/com/cvsgo/service/PromotionService.java @@ -3,8 +3,8 @@ import com.cvsgo.dto.promotion.ReadPromotionResponseDto; import com.cvsgo.repository.PromotionRepository; import java.time.LocalDateTime; +import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,9 +21,10 @@ public class PromotionService { * @return 프로모션 목록 */ @Transactional(readOnly = true) - public Page readPromotionList(Pageable pageable) { - return promotionRepository.findActivePromotions(LocalDateTime.now(), pageable) - .map(ReadPromotionResponseDto::from); + public List readPromotionList() { + List promotionResponseDtos = promotionRepository.findActivePromotions(LocalDateTime.now()).stream() + .map(ReadPromotionResponseDto::from).toList(); + return promotionResponseDtos; } } diff --git a/src/test/java/com/cvsgo/controller/PromotionControllerTest.java b/src/test/java/com/cvsgo/controller/PromotionControllerTest.java index e6380410..4a9e6f9a 100644 --- a/src/test/java/com/cvsgo/controller/PromotionControllerTest.java +++ b/src/test/java/com/cvsgo/controller/PromotionControllerTest.java @@ -3,7 +3,6 @@ import static com.cvsgo.ApiDocumentUtils.documentIdentifier; import static com.cvsgo.ApiDocumentUtils.getDocumentRequest; import static com.cvsgo.ApiDocumentUtils.getDocumentResponse; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; @@ -27,7 +26,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.domain.PageImpl; import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; @@ -69,7 +67,7 @@ void setup(WebApplicationContext webApplicationContext, @DisplayName("프로모션 목록을 정상적으로 조회하면 HTTP 200을 응답한다") void respond_200_when_read_promotion_list_successfully() throws Exception { List responseDto = List.of(new ReadPromotionResponseDto(promotion1), new ReadPromotionResponseDto(promotion2)); - given(promotionService.readPromotionList(any())).willReturn(new PageImpl<>(responseDto)); + given(promotionService.readPromotionList()).willReturn(responseDto); mockMvc.perform(get("/api/promotions").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) @@ -78,9 +76,9 @@ void respond_200_when_read_promotion_list_successfully() throws Exception { getDocumentRequest(), getDocumentResponse(), relaxedResponseFields( - fieldWithPath("data.content[].id").type(JsonFieldType.NUMBER).description("프로모션 ID"), - fieldWithPath("data.content[].imageUrl").type(JsonFieldType.STRING).description("프로모션 이미지 url"), - fieldWithPath("data.content[].landingUrl").type(JsonFieldType.STRING).description("프로모션 랜딩 url") + fieldWithPath("data.[].id").type(JsonFieldType.NUMBER).description("프로모션 ID"), + fieldWithPath("data.[].imageUrl").type(JsonFieldType.STRING).description("프로모션 이미지 url"), + fieldWithPath("data.[].landingUrl").type(JsonFieldType.STRING).description("프로모션 랜딩 url") ) )); } diff --git a/src/test/java/com/cvsgo/repository/PromotionRepositoryTest.java b/src/test/java/com/cvsgo/repository/PromotionRepositoryTest.java index e96693d6..f4e0f5ec 100644 --- a/src/test/java/com/cvsgo/repository/PromotionRepositoryTest.java +++ b/src/test/java/com/cvsgo/repository/PromotionRepositoryTest.java @@ -13,8 +13,6 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; @Import(TestConfig.class) @DataJpaTest @@ -41,14 +39,13 @@ void initData() { @DisplayName("활성된 프로모션을 조회한다") void find_active_promotions() { // when - Page foundPromotions = promotionRepository.findActivePromotions( - LocalDateTime.now(), PageRequest.of(0, 20)); + LocalDateTime now = LocalDateTime.now(); + List foundPromotions = promotionRepository.findActivePromotions(now); // then - assertThat(foundPromotions).isNotEmpty(); - assertThat(foundPromotions.getContent()).hasSize(2); - assertThat(foundPromotions.getContent().get(0).getPriority()).isEqualTo(1); - assertThat(foundPromotions.getContent().get(1).getPriority()).isEqualTo(2); + assertThat(foundPromotions).hasSize(2); + assertThat(foundPromotions.get(0).getPriority()).isEqualTo(1); + assertThat(foundPromotions.get(1).getPriority()).isEqualTo(2); } private Promotion promotion1; diff --git a/src/test/java/com/cvsgo/service/PromotionServiceTest.java b/src/test/java/com/cvsgo/service/PromotionServiceTest.java index 12eebe84..9c194170 100644 --- a/src/test/java/com/cvsgo/service/PromotionServiceTest.java +++ b/src/test/java/com/cvsgo/service/PromotionServiceTest.java @@ -9,6 +9,7 @@ import com.cvsgo.dto.promotion.ReadPromotionResponseDto; import com.cvsgo.entity.Promotion; import com.cvsgo.repository.PromotionRepository; +import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,10 +17,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; @ExtendWith(MockitoExtension.class) class PromotionServiceTest { @@ -33,13 +30,13 @@ class PromotionServiceTest { @Test @DisplayName("프로모션 목록을 정상적으로 조회한다") void succeed_to_read_promotion_list() { - Pageable pageable = PageRequest.of(0, 20); - given(promotionRepository.findActivePromotions(any(), any())).willReturn(getPromotionList()); + given(promotionRepository.findActivePromotions(any())).willReturn(getPromotionList()); - Page result = promotionService.readPromotionList(pageable); + List result = promotionService.readPromotionList(); - assertEquals(getPromotionList().getTotalElements(), result.getTotalElements()); - then(promotionRepository).should(times(1)).findActivePromotions(any(), any()); + assertEquals(2, result.size()); + + then(promotionRepository).should(times(1)).findActivePromotions(any(LocalDateTime.class)); } Promotion promotion1 = Promotion.builder() @@ -54,7 +51,7 @@ void succeed_to_read_promotion_list() { .landingUrl("landindUrl2") .build(); - private Page getPromotionList() { - return new PageImpl<>(List.of(promotion1, promotion2)); + private List getPromotionList() { + return List.of(promotion1, promotion2); } }