From cb35cc98a5d30c6704b40958d53b345baac4df0a Mon Sep 17 00:00:00 2001 From: yummygyudon Date: Fri, 27 Dec 2024 21:03:50 +0900 Subject: [PATCH 1/9] =?UTF-8?q?[CHORE]=20PublishPeriod=20&=20BannerImage?= =?UTF-8?q?=20=EB=8F=84=EB=A9=94=EC=9D=B8=20builder=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/sopt/makers/operation/banner/BannerTest.java | 11 ++++------- .../makers/operation/banner/PublishPeriodTest.java | 3 ++- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/operation-domain/src/test/java/org/sopt/makers/operation/banner/BannerTest.java b/operation-domain/src/test/java/org/sopt/makers/operation/banner/BannerTest.java index f8cdb1c7..ea0d7322 100644 --- a/operation-domain/src/test/java/org/sopt/makers/operation/banner/BannerTest.java +++ b/operation-domain/src/test/java/org/sopt/makers/operation/banner/BannerTest.java @@ -47,13 +47,10 @@ public class BannerTest { private static final LocalDate TEST_BANNER_END_DATE = LocalDate.of(2024, 12, 31); private static final String TEST_BANNER_PC_IMAGE_URL = "image-url-for-pc"; private static final String TEST_BANNER_MOBILE_IMAGE_URL = "image-url-for-mobile"; - - private static final PublishPeriod TEST_PUBLISH_PERIOD = new PublishPeriod( - TEST_BANNER_START_DATE, TEST_BANNER_END_DATE - ); - private static final BannerImage TEST_BANNER_IMAGE = new BannerImage( - TEST_BANNER_PC_IMAGE_URL, TEST_BANNER_MOBILE_IMAGE_URL - ); + private static final PublishPeriod TEST_PUBLISH_PERIOD = PublishPeriod.builder() + .startDate(TEST_BANNER_START_DATE).endDate(TEST_BANNER_END_DATE).build(); + private static final BannerImage TEST_BANNER_IMAGE = BannerImage.builder() + .pcImageUrl(TEST_BANNER_PC_IMAGE_URL).mobileImageUrl(TEST_BANNER_MOBILE_IMAGE_URL).build(); private static final Banner TEST_BANNER = Banner.builder() .location(TEST_BANNER_LOCATION) diff --git a/operation-domain/src/test/java/org/sopt/makers/operation/banner/PublishPeriodTest.java b/operation-domain/src/test/java/org/sopt/makers/operation/banner/PublishPeriodTest.java index f0d6a3da..1825ec4f 100644 --- a/operation-domain/src/test/java/org/sopt/makers/operation/banner/PublishPeriodTest.java +++ b/operation-domain/src/test/java/org/sopt/makers/operation/banner/PublishPeriodTest.java @@ -17,7 +17,8 @@ public class PublishPeriodTest { private static final LocalDate TEST_START_DATE = LocalDate.of(2024,1,1); private static final LocalDate TEST_END_DATE = LocalDate.of(2024,12,31); - private final PublishPeriod givenPeriod = new PublishPeriod(TEST_START_DATE, TEST_END_DATE); + private final PublishPeriod givenPeriod = PublishPeriod.builder() + .startDate(TEST_START_DATE).endDate(TEST_END_DATE).build(); @ParameterizedTest(name = "({index}) date : {0} -> result : {1}") @MethodSource("argsForCalculateStatus") From f09630bddee4c27e4e2d783d16161bb8c5cd6bd7 Mon Sep 17 00:00:00 2001 From: yummygyudon Date: Sat, 28 Dec 2024 20:10:25 +0900 Subject: [PATCH 2/9] =?UTF-8?q?[FIX]=20`PublishStatus`=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20Enum=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 잘못된 Return Type 수정 --- .../sopt/makers/operation/banner/domain/PublishStatus.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/banner/domain/PublishStatus.java b/operation-domain/src/main/java/org/sopt/makers/operation/banner/domain/PublishStatus.java index 87286b6b..91ff74f4 100644 --- a/operation-domain/src/main/java/org/sopt/makers/operation/banner/domain/PublishStatus.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/banner/domain/PublishStatus.java @@ -20,9 +20,9 @@ public enum PublishStatus { private final String value; - public static PublishLocation getByValue(String value) { - return Arrays.stream(PublishLocation.values()) - .filter(location -> location.getValue().equals(value)) + public static PublishStatus getByValue(String value) { + return Arrays.stream(PublishStatus.values()) + .filter(status -> status.getValue().equals(value)) .findAny().orElseThrow(() -> new BannerException(BannerFailureCode.NOT_FOUND_STATUS)); } } From b0c15463215fd1d648e2deca3b2f978d2cb6143a Mon Sep 17 00:00:00 2001 From: yummygyudon Date: Sat, 28 Dec 2024 20:11:03 +0900 Subject: [PATCH 3/9] =?UTF-8?q?[FEAT]=20=EB=B0=B0=EB=84=88=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../operation/web/banner/api/BannerApi.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApi.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApi.java index 7109f7f7..de4e73f3 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApi.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApi.java @@ -32,6 +32,25 @@ public interface BannerApi { ) ResponseEntity> getBannerDetail(Long bannerId); + @Operation( + summary = "배너 목록 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "배너 목록 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청

1. 존재하지 않는 Parameter Key
2. 존재하지 않는 Parameter Value" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getBanners(String status, String sort); + @Operation( summary = "배너 삭제 API", responses = { From efedc20466f52bf249659b1d0f361937569f5b45 Mon Sep 17 00:00:00 2001 From: yummygyudon Date: Sat, 28 Dec 2024 20:12:41 +0900 Subject: [PATCH 4/9] =?UTF-8?q?[FEAT]=20Banner=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20Success=20/=20Failure=20Code=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2가지 조회 Request Parameter 오류에 대한 Failure Code를 선언했습니다. - Filter 파라미터 - Sort 파라미터 --- .../sopt/makers/operation/code/failure/BannerFailureCode.java | 4 +++- .../makers/operation/code/success/web/BannerSuccessCode.java | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/BannerFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/BannerFailureCode.java index d25ad9f8..6a03c02d 100644 --- a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/BannerFailureCode.java +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/BannerFailureCode.java @@ -19,8 +19,10 @@ public enum BannerFailureCode implements FailureCode { NOT_FOUNT_BANNER(NOT_FOUND, "존재하지 않는 배너입니다."), NOT_SUPPORTED_PLATFORM_TYPE(NOT_FOUND, "지원하지 않는 플랫폼 유형입니다."), NOT_FOUND_BANNER(NOT_FOUND, "존재하지 않는 배너입니다."), - NOT_FOUND_BANNER_IMAGE(NOT_FOUND, "존재하지 않는 배너 이미지입니다.") + NOT_FOUND_BANNER_IMAGE(NOT_FOUND, "존재하지 않는 배너 이미지입니다."), + INVALID_BANNER_PROGRESS_STATUS_PARAMETER(BAD_REQUEST, "올바르지 않은 배너 진행 상태 조건입니다."), + INVALID_BANNER_SORT_CRITERIA_PARAMETER(BAD_REQUEST, "올바르지 않은 배너 정렬 조건입니다."), ; private final HttpStatus status; diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/BannerSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/BannerSuccessCode.java index 8c2f063f..793b6884 100644 --- a/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/BannerSuccessCode.java +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/BannerSuccessCode.java @@ -11,6 +11,7 @@ @RequiredArgsConstructor(access = PRIVATE) public enum BannerSuccessCode implements SuccessCode { SUCCESS_GET_BANNER_DETAIL(HttpStatus.OK, "배너 상세 정보 조회 성공"), + SUCCESS_GET_BANNER_LIST(HttpStatus.OK, "배너 목록 정보 조회 성공"), SUCCESS_DELETE_BANNER(HttpStatus. NO_CONTENT, "배너 삭제 성공"), SUCCESS_GET_EXTERNAL_BANNERS(HttpStatus.OK, "외부 배너 조회 성공"), SUCCESS_GET_BANNER_IMAGE_PRE_SIGNED_URL(HttpStatus.OK, "이미지 업로드 pre signed url 조회에 성공했습니다"), From 81e553ec6ca7eb4f969e79eb5a74965013d005c2 Mon Sep 17 00:00:00 2001 From: yummygyudon Date: Sat, 28 Dec 2024 20:42:07 +0900 Subject: [PATCH 5/9] =?UTF-8?q?[FEAT]=20Banner=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20Service=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 요청에 따른 동적 Filtering & Sorting 분기가 필요해 SQL Query를 통한 동적 쿼리 적용보다 Application 단에서의 조건 분기로 처리하는 것이 적합하다고 생각하여 메서드를 통한 전처리를 진행했습니다. --- .../web/banner/service/BannerService.java | 46 ++++++++++++++++++- .../web/banner/service/BannerServiceImpl.java | 36 ++++++++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerService.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerService.java index 7c14ae53..006d1403 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerService.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerService.java @@ -1,10 +1,12 @@ package org.sopt.makers.operation.web.banner.service; +import java.util.Arrays; import java.util.List; +import org.sopt.makers.operation.code.failure.BannerFailureCode; +import org.sopt.makers.operation.exception.BannerException; import org.sopt.makers.operation.web.banner.dto.request.*; import org.sopt.makers.operation.web.banner.dto.response.BannerResponse; -import org.sopt.makers.operation.web.banner.dto.response.BannerResponse.BannerDetail; import org.sopt.makers.operation.web.banner.dto.response.BannerResponse.BannerImageUrl; public interface BannerService { @@ -18,5 +20,47 @@ public interface BannerService { BannerResponse.ImagePreSignedUrl getIssuedPreSignedUrlForPutImage(String contentName, String imageType, String imageExtension, String contentType); BannerResponse.BannerDetail createBanner(BannerRequest.BannerCreate request); + + List getBanners(final FilterCriteria status, final SortCriteria sort); + + enum FilterCriteria { + ALL("all"), + RESERVED("reserved"), + IN_PROGRESS("in_progress"), + DONE("done"), + ; + private final String parameter; + + FilterCriteria(String parameter) { + this.parameter = parameter; + } + + public String getParameter() { + return this.parameter; + } + + public static FilterCriteria fromParameter(String parameter) { + return Arrays.stream(FilterCriteria.values()) + .filter(value -> value.parameter.equals(parameter)) + .findAny().orElseThrow(() -> new BannerException(BannerFailureCode.INVALID_BANNER_PROGRESS_STATUS_PARAMETER)); + } + + } + enum SortCriteria { + START_DATE("start_date"), + END_DATE("end_date"), + STATUS("status") + ; + private final String parameter; + + SortCriteria(String parameter) { + this.parameter = parameter; + } + public static SortCriteria fromParameter(String parameter) { + return Arrays.stream(SortCriteria.values()) + .filter(value -> value.parameter.equals(parameter)) + .findAny().orElseThrow(() -> new BannerException(BannerFailureCode.INVALID_BANNER_SORT_CRITERIA_PARAMETER)); + } + } } diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java index ac40c5cd..8ac07909 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java @@ -1,5 +1,6 @@ package org.sopt.makers.operation.web.banner.service; +import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -9,8 +10,6 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import lombok.RequiredArgsConstructor; -import lombok.val; import org.sopt.makers.operation.banner.domain.*; import org.sopt.makers.operation.banner.repository.BannerRepository; @@ -100,6 +99,39 @@ public BannerDetail createBanner(BannerCreate request) { return BannerResponse.BannerDetail.fromEntity(banner); } + @Override + public List getBanners(FilterCriteria filter, SortCriteria sort) { + val allBanners = bannerRepository.findAll(); + val filteredBanners = getFilteredBanners(allBanners, filter); + val resultBanners = getSortedBanners(filteredBanners, sort); + return resultBanners.stream() + .map(BannerSimple::fromEntity) + .toList(); + } + + private List getFilteredBanners(List banners, FilterCriteria filter) { + val targetStatus = PublishStatus.getByValue(filter.getParameter()); + if (targetStatus == null) { + return banners; + } + return banners.stream() + .filter(banner -> targetStatus.equals(banner.getPeriod().getPublishStatus(LocalDate.now()))) + .toList(); + } + + private List getSortedBanners(List banners, SortCriteria criteria) { + return switch (criteria) { + case STATUS, START_DATE -> banners.stream().sorted( + Comparator.comparing(Banner::getPeriod, (p1, p2) -> p2.getStartDate().compareTo(p1.getStartDate())) + .thenComparing(Banner::getPeriod, Comparator.comparing(PublishPeriod::getEndDate)) + ).toList(); + case END_DATE -> banners.stream().sorted( + Comparator.comparing(Banner::getPeriod, Comparator.comparing(PublishPeriod::getEndDate)) + .thenComparing(Banner::getPeriod, (p1, p2) -> p2.getStartDate().compareTo(p1.getStartDate())) + ).toList(); + }; + } + private PublishPeriod getPublishPeriod(LocalDate startDate, LocalDate endDate) { return PublishPeriod.builder() .startDate(startDate) From 7e9ea0846ecb442945c5d51dd5bd1908ec1c2c42 Mon Sep 17 00:00:00 2001 From: yummygyudon Date: Sat, 28 Dec 2024 20:42:26 +0900 Subject: [PATCH 6/9] =?UTF-8?q?[FEAT]=20Banner=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/banner/api/BannerApiController.java | 23 ++++++++++++------ .../banner/dto/response/BannerResponse.java | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApiController.java index 3e950e42..0232a8cf 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApiController.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/api/BannerApiController.java @@ -3,12 +3,11 @@ import lombok.RequiredArgsConstructor; import lombok.val; -import org.sopt.makers.operation.code.success.web.BannerSuccessCode; import org.sopt.makers.operation.dto.BaseResponse; import org.sopt.makers.operation.util.ApiResponseUtil; import org.sopt.makers.operation.web.banner.dto.request.BannerRequest; -import org.sopt.makers.operation.web.banner.dto.request.BannerRequest.BannerCreate; import org.sopt.makers.operation.web.banner.service.BannerService; +import org.sopt.makers.operation.web.banner.service.BannerService.FilterCriteria; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -17,11 +16,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_CREATE_BANNER; -import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_DELETE_BANNER; -import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_GET_BANNER_DETAIL; -import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_GET_BANNER_IMAGE_PRE_SIGNED_URL; -import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.SUCCESS_GET_EXTERNAL_BANNERS; +import static org.sopt.makers.operation.code.success.web.BannerSuccessCode.*; +import static org.sopt.makers.operation.web.banner.service.BannerService.*; import org.springframework.web.bind.annotation.*; @@ -41,6 +37,19 @@ public ResponseEntity> getBannerDetail( return ApiResponseUtil.success(SUCCESS_GET_BANNER_DETAIL, response); } + @Override + @GetMapping + public ResponseEntity> getBanners( + @RequestParam(value = "filter", required = false, defaultValue = "all") String filterCriteriaParameter, + @RequestParam(value = "sort", required = false, defaultValue = "status") String sortCriteriaParameter + ) { + val progressStatus = FilterCriteria.fromParameter(filterCriteriaParameter); + val sortCriteria = SortCriteria.fromParameter(sortCriteriaParameter); + + val response = bannerService.getBanners(progressStatus, sortCriteria); + return ApiResponseUtil.success(SUCCESS_GET_BANNER_LIST, response); + } + @Override @DeleteMapping("/{bannerId}") public ResponseEntity> deleteBanner( diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/dto/response/BannerResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/dto/response/BannerResponse.java index 6f60e2a5..0da61b52 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/dto/response/BannerResponse.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/dto/response/BannerResponse.java @@ -14,6 +14,30 @@ @RequiredArgsConstructor(access = PRIVATE) public final class BannerResponse { + @Builder(access = PRIVATE) + public record BannerSimple ( + @JsonProperty("id") long bannerId, + @JsonProperty("status") String bannerStatus, + @JsonProperty("location") String bannerLocation, + @JsonProperty("content_type") String bannerType, + @JsonProperty("publisher") String publisher, + @JsonProperty("start_date") LocalDate startDate, + @JsonProperty("end_date") LocalDate endDate + ) { + public static BannerSimple fromEntity(Banner banner) { + return BannerSimple.builder() + .bannerId(banner.getId()) + .bannerStatus(banner.getPeriod().getPublishStatus(LocalDate.now()).getValue()) + .bannerLocation(banner.getLocation().getValue()) + .bannerType(banner.getContentType().getValue()) + .publisher(banner.getPublisher()) + .startDate(banner.getPeriod().getStartDate()) + .endDate(banner.getPeriod().getEndDate()) + .build(); + } + } + + @Builder(access = PRIVATE) public record BannerDetail( @JsonProperty("id") long bannerId, From 43a04ec359457d7b7c7e16fecba2aaa912fb8ba7 Mon Sep 17 00:00:00 2001 From: yummygyudon Date: Sat, 28 Dec 2024 20:57:07 +0900 Subject: [PATCH 7/9] =?UTF-8?q?[TEST]=20=EB=B0=B0=EB=84=88=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=A0=95=EC=83=81=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F?= =?UTF-8?q?=20=EC=9D=98=EB=8F=84=20=EA=B2=B0=EA=B3=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20TEST?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 복수의 초기 데이터가 필요해 정적 SQL 파일을 통해 Dummy Data를 주입했습니다. --- .../data/insert-get-banners-data.sql | 57 ++++++++++++++++++ .../web/banner/service/BannerServiceTest.java | 59 +++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 operation-api/src/main/resources/data/insert-get-banners-data.sql create mode 100644 operation-api/src/test/java/org/sopt/makers/operation/web/banner/service/BannerServiceTest.java diff --git a/operation-api/src/main/resources/data/insert-get-banners-data.sql b/operation-api/src/main/resources/data/insert-get-banners-data.sql new file mode 100644 index 00000000..3daed724 --- /dev/null +++ b/operation-api/src/main/resources/data/insert-get-banners-data.sql @@ -0,0 +1,57 @@ +CREATE TABLE if not exists banners ( + id bigserial not null, + created_date timestamp(6), + last_modified_date timestamp(6), + content_type varchar(255) not null, + img_url_mobile varchar(255) not null, + img_url_pc varchar(255) not null, + link varchar(255), + location varchar(255) not null, + end_date date not null, + start_date date not null, + publisher varchar(255) not null, + primary key (id) +); + +DELETE FROM banners; + +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2031-06-10', '2031-06-17','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2031-06-01', '2031-06-30','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2031-01-01', '2031-06-30','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2030-01-01', '2030-12-31','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2030-01-01', '2030-06-30','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2030-01-01', '2030-12-31','user'); + +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2024-12-01', '2024-12-31','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2024-12-01', '2025-12-01','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2024-01-01', '2025-06-30','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2024-06-30', '2025-06-30','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2024-06-30', '2025-01-01','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2024-01-01', '2025-01-01','user'); + +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2011-06-10', '2011-06-17','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2011-06-01', '2011-06-30','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2011-01-01', '2011-06-30','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2010-01-01', '2011-12-31','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2010-01-01', '2010-06-30','user'); +INSERT INTO banners (content_type, img_url_mobile, img_url_pc, location, start_date, end_date, publisher) +VALUES ('PRODUCT', '','','PLAYGROUND_COMMUNITY','2010-01-01', '2010-12-31','user'); + + diff --git a/operation-api/src/test/java/org/sopt/makers/operation/web/banner/service/BannerServiceTest.java b/operation-api/src/test/java/org/sopt/makers/operation/web/banner/service/BannerServiceTest.java new file mode 100644 index 00000000..7f778722 --- /dev/null +++ b/operation-api/src/test/java/org/sopt/makers/operation/web/banner/service/BannerServiceTest.java @@ -0,0 +1,59 @@ +package org.sopt.makers.operation.web.banner.service; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.sopt.makers.operation.banner.domain.PublishPeriod; +import org.sopt.makers.operation.banner.domain.PublishStatus; +import org.sopt.makers.operation.web.banner.dto.response.BannerResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sopt.makers.operation.web.banner.service.BannerService.*; + +@SpringBootTest +@ActiveProfiles("test") +@Sql(scripts = "classpath:data/insert-get-banners-data.sql") +class BannerServiceTest { + + @Autowired + private BannerService bannerService; + + + @ParameterizedTest + @MethodSource("argsForGetBanners") + void getBanners( + FilterCriteria givenFilter, + SortCriteria givenSort, + int expectedSize, + PublishStatus expectedStatus + ) { + List banners = bannerService.getBanners(givenFilter, givenSort); + + assertThat(banners) + .hasSize(expectedSize) + .extracting(info -> PublishPeriod.builder().startDate(info.startDate()).endDate(info.endDate()).build()) + .allMatch(period -> { + if (givenFilter.equals(FilterCriteria.ALL)) { + return true; + } + return period.getPublishStatus(LocalDate.now()).equals(expectedStatus); + }); + } + static Stream argsForGetBanners(){ + return Stream.of( + Arguments.of(FilterCriteria.RESERVED, SortCriteria.START_DATE, 6, PublishStatus.RESERVED), + Arguments.of(FilterCriteria.ALL, SortCriteria.START_DATE, 18, null), + Arguments.of(FilterCriteria.IN_PROGRESS, SortCriteria.START_DATE, 6, PublishStatus.IN_PROGRESS), + Arguments.of(FilterCriteria.DONE, SortCriteria.START_DATE, 6, PublishStatus.DONE) + ); + } + +} \ No newline at end of file From 8328cd77e406bc9a37d4ab4998cf3435121f40a0 Mon Sep 17 00:00:00 2001 From: yummygyudon Date: Sat, 28 Dec 2024 20:58:08 +0900 Subject: [PATCH 8/9] =?UTF-8?q?[REFACTOR]=20=EC=A0=84=EC=B2=B4=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=97=90=20=EB=8C=80=ED=95=9C=20Filter=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=EC=97=90=20=EB=8C=80=ED=95=B4=20Early=20Return=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../operation/web/banner/service/BannerServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java index 8ac07909..0c516898 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java @@ -110,10 +110,10 @@ public List getBanners(FilterCriteria filter, SortCriteria sort) { } private List getFilteredBanners(List banners, FilterCriteria filter) { - val targetStatus = PublishStatus.getByValue(filter.getParameter()); - if (targetStatus == null) { + if (FilterCriteria.ALL.equals(filter)) { return banners; } + val targetStatus = PublishStatus.getByValue(filter.getParameter()); return banners.stream() .filter(banner -> targetStatus.equals(banner.getPeriod().getPublishStatus(LocalDate.now()))) .toList(); From af79539eb359a6cc70fb7e55b4a579e349c47172 Mon Sep 17 00:00:00 2001 From: yummygyudon Date: Tue, 14 Jan 2025 21:27:07 +0900 Subject: [PATCH 9/9] =?UTF-8?q?[FIX]=20BannerServiceImpl=20-=20System=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 `LocalDate.now()` 사용으로 인해 프로덕션/테스트 환경 모두 런타임 환경 기준으로만 실행되는 의존성을 Clock 빈 객체 주입 방식으로 수정 - 테스트 용이성을 높였습니다. - 기존 의존적인 방식으로 인한 Test 실패를 해결했습니다. --- .../web/banner/service/BannerServiceImpl.java | 9 ++++---- .../web/banner/service/BannerServiceTest.java | 22 +++++++++++++++++-- .../makers/operation/config/TimeConfig.java | 16 ++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 operation-common/src/main/java/org/sopt/makers/operation/config/TimeConfig.java diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java index 0c516898..d0ade422 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/banner/service/BannerServiceImpl.java @@ -1,8 +1,8 @@ package org.sopt.makers.operation.web.banner.service; +import java.time.Clock; import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.val; import org.sopt.makers.operation.banner.domain.Banner; @@ -31,6 +31,7 @@ public class BannerServiceImpl implements BannerService { private final BannerRepository bannerRepository; private final S3Service s3Service; private final ValueConfig valueConfig; + private final Clock clock; @Override public BannerResponse.BannerDetail getBannerDetail(final long bannerId) { @@ -52,7 +53,7 @@ public List getExternalBanners(final String image List list = bannerList.stream() .map( banner -> banner.getImage().retrieveImageUrl(imageType)) - .collect(Collectors.toUnmodifiableList()); + .toList(); return BannerResponse.BannerImageUrl.fromEntity(list); } @@ -74,7 +75,7 @@ public BannerResponse.ImagePreSignedUrl getIssuedPreSignedUrlForPutImage(String } private String getBannerImageName(String location, String contentName, String imageType, String imageExtension) { - val today = LocalDate.now(); + val today = LocalDate.now(clock); val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); val formattedDate = today.format(formatter); @@ -115,7 +116,7 @@ private List getFilteredBanners(List banners, FilterCriteria fil } val targetStatus = PublishStatus.getByValue(filter.getParameter()); return banners.stream() - .filter(banner -> targetStatus.equals(banner.getPeriod().getPublishStatus(LocalDate.now()))) + .filter(banner -> targetStatus.equals(banner.getPeriod().getPublishStatus(LocalDate.now(clock)))) .toList(); } diff --git a/operation-api/src/test/java/org/sopt/makers/operation/web/banner/service/BannerServiceTest.java b/operation-api/src/test/java/org/sopt/makers/operation/web/banner/service/BannerServiceTest.java index 7f778722..dad8b0e2 100644 --- a/operation-api/src/test/java/org/sopt/makers/operation/web/banner/service/BannerServiceTest.java +++ b/operation-api/src/test/java/org/sopt/makers/operation/web/banner/service/BannerServiceTest.java @@ -1,17 +1,25 @@ package org.sopt.makers.operation.web.banner.service; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.sopt.makers.operation.banner.domain.PublishPeriod; import org.sopt.makers.operation.banner.domain.PublishStatus; +import org.sopt.makers.operation.banner.repository.BannerRepository; +import org.sopt.makers.operation.client.s3.S3Service; +import org.sopt.makers.operation.config.ValueConfig; import org.sopt.makers.operation.web.banner.dto.response.BannerResponse; 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.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import java.time.LocalDate; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; import java.util.List; import java.util.stream.Stream; @@ -22,10 +30,20 @@ @ActiveProfiles("test") @Sql(scripts = "classpath:data/insert-get-banners-data.sql") class BannerServiceTest { - + private static final Clock TEST_CLOCK = Clock.fixed(Instant.parse("2024-12-28T00:00:00Z"), ZoneId.systemDefault()); + @Autowired + private BannerRepository bannerRepository; + @MockBean + private S3Service s3Service; @Autowired + private ValueConfig valueConfig; + private BannerService bannerService; + @BeforeEach + void setBannerService() { + bannerService = new BannerServiceImpl(bannerRepository, s3Service, valueConfig, TEST_CLOCK); + } @ParameterizedTest @MethodSource("argsForGetBanners") @@ -44,7 +62,7 @@ void getBanners( if (givenFilter.equals(FilterCriteria.ALL)) { return true; } - return period.getPublishStatus(LocalDate.now()).equals(expectedStatus); + return period.getPublishStatus(LocalDate.now(TEST_CLOCK)).equals(expectedStatus); }); } static Stream argsForGetBanners(){ diff --git a/operation-common/src/main/java/org/sopt/makers/operation/config/TimeConfig.java b/operation-common/src/main/java/org/sopt/makers/operation/config/TimeConfig.java new file mode 100644 index 00000000..2a2fe95a --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/config/TimeConfig.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.Clock; + +@Configuration +public class TimeConfig { + + @Bean + public Clock clock() { + return Clock.systemDefaultZone(); + } + +}