diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/code/LiquorCtrResultCode.java b/src/main/java/com/woowacamp/soolsool/core/liquor/code/LiquorCtrResultCode.java index 962e0b1e..baa97411 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/code/LiquorCtrResultCode.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/code/LiquorCtrResultCode.java @@ -9,8 +9,10 @@ @Getter @RequiredArgsConstructor public enum LiquorCtrResultCode implements ResultCode { - - FIND_LQUOR_CTR_SUCCESS(OK.value(), "LC001", "술 클릭률 조회에 성공했습니다."), + + FIND_LIQUOR_CTR_SUCCESS(OK.value(), "LC001", "술 클릭률 조회에 성공했습니다."), + INCREASE_IMPRESSION_SUCCESS(OK.value(), "LC002", "술 노출수 증가에 성공했습니다."), + INCREASE_CLICK_SUCCESS(OK.value(), "LC003", "술 클릭수 증가에 성공했습니다."), ; private final int status; diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/code/LiquorResultCode.java b/src/main/java/com/woowacamp/soolsool/core/liquor/code/LiquorResultCode.java index 7460e078..d2b2882d 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/code/LiquorResultCode.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/code/LiquorResultCode.java @@ -17,6 +17,7 @@ public enum LiquorResultCode implements ResultCode { LIQUOR_LIST_FOUND(OK.value(), "L103", "술 목록이 정상적으로 검색되었습니다."), LIQUOR_UPDATED(OK.value(), "L104", "술 상품이 정상적으로 수정되었습니다."), LIQUOR_DELETED(NO_CONTENT.value(), "L105", "술 상품이 정상적으로 삭제되었습니다."), + LIQUOR_PURCHASED_TOGETHER_FOUND(OK.value(), "L106", "연관 상품 목록이 정상적으로 검색되었습니다."), ; private final int status; diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/controller/LiquorController.java b/src/main/java/com/woowacamp/soolsool/core/liquor/controller/LiquorController.java index 51930303..f187db02 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/controller/LiquorController.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/controller/LiquorController.java @@ -10,6 +10,7 @@ import com.woowacamp.soolsool.core.liquor.domain.vo.LiquorRegionType; import com.woowacamp.soolsool.core.liquor.domain.vo.LiquorStatusType; import com.woowacamp.soolsool.core.liquor.dto.LiquorDetailResponse; +import com.woowacamp.soolsool.core.liquor.dto.LiquorElementResponse; import com.woowacamp.soolsool.core.liquor.dto.LiquorModifyRequest; import com.woowacamp.soolsool.core.liquor.dto.LiquorSaveRequest; import com.woowacamp.soolsool.core.liquor.dto.PageLiquorResponse; @@ -19,6 +20,7 @@ import com.woowacamp.soolsool.global.auth.dto.Vendor; import com.woowacamp.soolsool.global.common.ApiResponse; import java.net.URI; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; @@ -68,6 +70,19 @@ public ResponseEntity> liquorDetail( return ResponseEntity.ok(ApiResponse.of(LiquorResultCode.LIQUOR_DETAIL_FOUND, response)); } + @NoAuth + @RequestLogging + @GetMapping("/{liquorId}/related") + public ResponseEntity>> liquorPurchasedTogether( + @PathVariable final Long liquorId + ) { + final List response = + liquorService.liquorPurchasedTogether(liquorId); + + return ResponseEntity.ok( + ApiResponse.of(LiquorResultCode.LIQUOR_PURCHASED_TOGETHER_FOUND, response)); + } + @NoAuth @RequestLogging @GetMapping("/first") diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/controller/LiquorCtrController.java b/src/main/java/com/woowacamp/soolsool/core/liquor/controller/LiquorCtrController.java index e1f0045a..45c9980d 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/controller/LiquorCtrController.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/controller/LiquorCtrController.java @@ -1,7 +1,9 @@ package com.woowacamp.soolsool.core.liquor.controller; import com.woowacamp.soolsool.core.liquor.code.LiquorCtrResultCode; -import com.woowacamp.soolsool.core.liquor.dto.response.LiquorCtrDetailResponse; +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorClickAddRequest; +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorCtrDetailResponse; +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorImpressionAddRequest; import com.woowacamp.soolsool.core.liquor.service.LiquorCtrService; import com.woowacamp.soolsool.global.aop.RequestLogging; import com.woowacamp.soolsool.global.auth.dto.NoAuth; @@ -9,6 +11,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -27,8 +31,30 @@ public ResponseEntity> findLiquorCtr( @RequestParam final Long liquorId ) { return ResponseEntity.ok( - ApiResponse.of(LiquorCtrResultCode.FIND_LQUOR_CTR_SUCCESS, + ApiResponse.of(LiquorCtrResultCode.FIND_LIQUOR_CTR_SUCCESS, new LiquorCtrDetailResponse(liquorCtrService.getLiquorCtrByLiquorId(liquorId))) ); } + + @NoAuth + @RequestLogging + @PatchMapping("/impressions") + public ResponseEntity> increaseImpression( + @RequestBody final LiquorImpressionAddRequest request + ) { + liquorCtrService.increaseImpression(request); + + return ResponseEntity.ok(ApiResponse.from(LiquorCtrResultCode.INCREASE_IMPRESSION_SUCCESS)); + } + + @NoAuth + @RequestLogging + @PatchMapping("/clicks") + public ResponseEntity> increaseClick( + @RequestBody final LiquorClickAddRequest request + ) { + liquorCtrService.increaseClick(request); + + return ResponseEntity.ok(ApiResponse.from(LiquorCtrResultCode.INCREASE_CLICK_SUCCESS)); + } } diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/dto/LiquorDetailResponse.java b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/LiquorDetailResponse.java index a46b89a2..b73ccaa4 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/dto/LiquorDetailResponse.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/LiquorDetailResponse.java @@ -1,8 +1,6 @@ package com.woowacamp.soolsool.core.liquor.dto; import com.woowacamp.soolsool.core.liquor.domain.Liquor; -import java.util.List; -import java.util.stream.Collectors; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -18,13 +16,8 @@ public class LiquorDetailResponse { private final Integer stock; private final Double alcohol; private final Integer volume; - private final List relatedLiquors; // TODO: 연관 상품 API 분리 - - public static LiquorDetailResponse of(final Liquor liquor, final List relatedLiquors) { - final List relatedLiquorResponses = relatedLiquors.stream() - .map(LiquorElementResponse::from) - .collect(Collectors.toList()); + public static LiquorDetailResponse of(final Liquor liquor) { return new LiquorDetailResponse( liquor.getId(), liquor.getName(), @@ -33,8 +26,7 @@ public static LiquorDetailResponse of(final Liquor liquor, final List re liquor.getImageUrl(), liquor.getTotalStock(), liquor.getAlcohol(), - liquor.getVolume(), - relatedLiquorResponses + liquor.getVolume() ); } } diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/dto/PageLiquorResponse.java b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/PageLiquorResponse.java index c79c2f58..bff2e8a5 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/dto/PageLiquorResponse.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/PageLiquorResponse.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.stream.Collectors; import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; @Getter diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/dto/liquorCtr/LiquorClickAddRequest.java b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/liquorCtr/LiquorClickAddRequest.java new file mode 100644 index 00000000..430ec074 --- /dev/null +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/liquorCtr/LiquorClickAddRequest.java @@ -0,0 +1,15 @@ +package com.woowacamp.soolsool.core.liquor.dto.liquorCtr; + +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.Getter; + +@Getter +public class LiquorClickAddRequest { + + private final Long liquorId; + + @JsonCreator + public LiquorClickAddRequest(final Long liquorId) { + this.liquorId = liquorId; + } +} diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/dto/response/LiquorCtrDetailResponse.java b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/liquorCtr/LiquorCtrDetailResponse.java similarity index 81% rename from src/main/java/com/woowacamp/soolsool/core/liquor/dto/response/LiquorCtrDetailResponse.java rename to src/main/java/com/woowacamp/soolsool/core/liquor/dto/liquorCtr/LiquorCtrDetailResponse.java index b3c57b94..ba9e28f1 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/dto/response/LiquorCtrDetailResponse.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/liquorCtr/LiquorCtrDetailResponse.java @@ -1,4 +1,4 @@ -package com.woowacamp.soolsool.core.liquor.dto.response; +package com.woowacamp.soolsool.core.liquor.dto.liquorCtr; import com.fasterxml.jackson.annotation.JsonCreator; import lombok.Getter; diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/dto/liquorCtr/LiquorImpressionAddRequest.java b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/liquorCtr/LiquorImpressionAddRequest.java new file mode 100644 index 00000000..e0cb065d --- /dev/null +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/dto/liquorCtr/LiquorImpressionAddRequest.java @@ -0,0 +1,16 @@ +package com.woowacamp.soolsool.core.liquor.dto.liquorCtr; + +import com.fasterxml.jackson.annotation.JsonCreator; +import java.util.List; +import lombok.Getter; + +@Getter +public class LiquorImpressionAddRequest { + + private final List liquorIds; + + @JsonCreator + public LiquorImpressionAddRequest(final List liquorIds) { + this.liquorIds = liquorIds; + } +} diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/repository/LiquorBrewCache.java b/src/main/java/com/woowacamp/soolsool/core/liquor/repository/LiquorBrewCache.java index 940b1eed..1d4a305a 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/repository/LiquorBrewCache.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/repository/LiquorBrewCache.java @@ -2,9 +2,7 @@ import com.woowacamp.soolsool.core.liquor.domain.LiquorBrew; import com.woowacamp.soolsool.core.liquor.domain.vo.LiquorBrewType; - import java.util.Optional; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.Cacheable; diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/repository/LiquorStatusCache.java b/src/main/java/com/woowacamp/soolsool/core/liquor/repository/LiquorStatusCache.java index 9b825b5f..f07ccc4c 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/repository/LiquorStatusCache.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/repository/LiquorStatusCache.java @@ -2,9 +2,7 @@ import com.woowacamp.soolsool.core.liquor.domain.LiquorStatus; import com.woowacamp.soolsool.core.liquor.domain.vo.LiquorStatusType; - import java.util.Optional; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.Cacheable; diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/service/LiquorCtrService.java b/src/main/java/com/woowacamp/soolsool/core/liquor/service/LiquorCtrService.java index f15a00da..f5db59cc 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/service/LiquorCtrService.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/service/LiquorCtrService.java @@ -1,6 +1,8 @@ package com.woowacamp.soolsool.core.liquor.service; import com.woowacamp.soolsool.core.liquor.domain.LiquorCtr; +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorClickAddRequest; +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorImpressionAddRequest; import com.woowacamp.soolsool.core.liquor.repository.LiquorCtrRepository; import com.woowacamp.soolsool.core.liquor.repository.redisson.LiquorCtrRedisRepository; import lombok.RequiredArgsConstructor; @@ -22,6 +24,14 @@ public double getLiquorCtrByLiquorId(final Long liquorId) { return liquorCtrRedisRepository.getCtr(liquorId); } + public void increaseImpression(final LiquorImpressionAddRequest request) { + request.getLiquorIds().forEach(liquorCtrRedisRepository::increaseImpression); + } + + public void increaseClick(final LiquorClickAddRequest request) { + liquorCtrRedisRepository.increaseClick(request.getLiquorId()); + } + @Transactional public void writeBackCtr(final LiquorCtr latestLiquorCtr) { liquorCtrRepository.updateLiquorCtr( diff --git a/src/main/java/com/woowacamp/soolsool/core/liquor/service/LiquorService.java b/src/main/java/com/woowacamp/soolsool/core/liquor/service/LiquorService.java index 6cf512c6..0324e15a 100644 --- a/src/main/java/com/woowacamp/soolsool/core/liquor/service/LiquorService.java +++ b/src/main/java/com/woowacamp/soolsool/core/liquor/service/LiquorService.java @@ -14,6 +14,7 @@ import com.woowacamp.soolsool.core.liquor.domain.vo.LiquorRegionType; import com.woowacamp.soolsool.core.liquor.domain.vo.LiquorStatusType; import com.woowacamp.soolsool.core.liquor.dto.LiquorDetailResponse; +import com.woowacamp.soolsool.core.liquor.dto.LiquorElementResponse; import com.woowacamp.soolsool.core.liquor.dto.LiquorModifyRequest; import com.woowacamp.soolsool.core.liquor.dto.LiquorSaveRequest; import com.woowacamp.soolsool.core.liquor.dto.LiquorSearchCondition; @@ -24,9 +25,9 @@ import com.woowacamp.soolsool.core.liquor.repository.LiquorRegionCache; import com.woowacamp.soolsool.core.liquor.repository.LiquorRepository; import com.woowacamp.soolsool.core.liquor.repository.LiquorStatusCache; -import com.woowacamp.soolsool.core.liquor.repository.redisson.LiquorCtrRedisRepository; import com.woowacamp.soolsool.global.exception.SoolSoolException; import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; @@ -49,8 +50,6 @@ public class LiquorService { private final LiquorCtrRepository liquorCtrRepository; private final LiquorQueryDslRepository liquorQueryDslRepository; - private final LiquorCtrRedisRepository liquorCtrRedisRepository; - @CacheEvict(value = "liquorsFirstPage") @Transactional public Long saveLiquor(final LiquorSaveRequest request) { @@ -71,12 +70,17 @@ public LiquorDetailResponse liquorDetail(final Long liquorId) { final Liquor liquor = liquorRepository.findById(liquorId) .orElseThrow(() -> new SoolSoolException(NOT_LIQUOR_FOUND)); + return LiquorDetailResponse.of(liquor); + } + + @Transactional(readOnly = true) + public List liquorPurchasedTogether(final Long liquorId) { final List relatedLiquors = liquorRepository .findLiquorsPurchasedTogether(liquorId, TOP_RANK_PAGEABLE); - liquorCtrRedisRepository.increaseClick(liquorId); - - return LiquorDetailResponse.of(liquor, relatedLiquors); + return relatedLiquors.stream() + .map(LiquorElementResponse::from) + .collect(Collectors.toList()); } @Transactional @@ -95,14 +99,9 @@ public PageLiquorResponse liquorList( brand ); - List liquors = liquorQueryDslRepository + final List liquors = liquorQueryDslRepository .getList(liquorSearchCondition, pageable, cursorId); - liquors.stream() - .map(Liquor::getId) - .sorted() - .forEach(liquorCtrRedisRepository::increaseImpression); - return PageLiquorResponse.of(pageable, liquors); } @@ -110,11 +109,6 @@ public PageLiquorResponse liquorList( public PageLiquorResponse getFirstPage(final Pageable pageable) { final List liquors = liquorQueryDslRepository.getCachedList(pageable); - liquors.stream() - .map(Liquor::getId) - .sorted() - .forEach(liquorCtrRedisRepository::increaseImpression); - return PageLiquorResponse.of(pageable, liquors); } diff --git a/src/test/java/com/woowacamp/soolsool/acceptance/LiquorAcceptanceTest.java b/src/test/java/com/woowacamp/soolsool/acceptance/LiquorAcceptanceTest.java index 8d9cb939..3c0c9711 100644 --- a/src/test/java/com/woowacamp/soolsool/acceptance/LiquorAcceptanceTest.java +++ b/src/test/java/com/woowacamp/soolsool/acceptance/LiquorAcceptanceTest.java @@ -149,14 +149,13 @@ void liquorDetail() { () -> assertThat(liquorDetailResponse.getImageUrl()).isEqualTo("/soju-url"), () -> assertThat(liquorDetailResponse.getStock()).isZero(), () -> assertThat(liquorDetailResponse.getAlcohol()).isEqualTo(12.0), - () -> assertThat(liquorDetailResponse.getVolume()).isEqualTo(300), - () -> assertThat(liquorDetailResponse.getRelatedLiquors()).isEmpty() + () -> assertThat(liquorDetailResponse.getVolume()).isEqualTo(300) ); } @Test - @DisplayName("술 상세정보를 연관된 상품과 함께 조회할 수 있다") - void liquorDetailWithRelatedLiquors() { + @DisplayName("특정 상품과 함께 많이 구매된 상품 목록을 조회할 수 있다") + void liquorPurchasedTogether() { // given String vendorAccessToken = RestAuthFixture.로그인_최민족_판매자(); Long 새로_Id = RestLiquorFixture.술_등록_새로_판매중(vendorAccessToken); @@ -172,29 +171,20 @@ void liquorDetailWithRelatedLiquors() { RestPayFixture.결제_성공(customerAccessToken, 주문서_Id); // when - LiquorDetailResponse liquorDetailResponse = RestAssured + List responses = RestAssured .given().log().all() .contentType(APPLICATION_JSON_VALUE) .accept(APPLICATION_JSON_VALUE) - .when().get("/api/liquors/{liquorId}", 새로_Id) + .when().get("/api/liquors/{liquorId}/related", 새로_Id) .then().log().all() - .extract().jsonPath().getObject("data", LiquorDetailResponse.class); + .extract().jsonPath().getList("data", LiquorElementResponse.class); - List relatedLiquorIds = liquorDetailResponse.getRelatedLiquors().stream() + List relatedLiquorIds = responses.stream() .map(LiquorElementResponse::getId) .collect(Collectors.toList()); // then - assertAll( - () -> assertThat(liquorDetailResponse.getName()).isEqualTo("새로"), - () -> assertThat(liquorDetailResponse.getPrice()).isEqualTo("3000"), - () -> assertThat(liquorDetailResponse.getBrand()).isEqualTo("롯데"), - () -> assertThat(liquorDetailResponse.getImageUrl()).isEqualTo("/soju-url"), - () -> assertThat(liquorDetailResponse.getStock()).isEqualTo(99), - () -> assertThat(liquorDetailResponse.getAlcohol()).isEqualTo(12.0), - () -> assertThat(liquorDetailResponse.getVolume()).isEqualTo(300), - () -> assertThat(relatedLiquorIds).containsExactlyInAnyOrder(얼음딸기주_Id) - ); + assertThat(relatedLiquorIds).containsExactlyInAnyOrder(얼음딸기주_Id); } @Test diff --git a/src/test/java/com/woowacamp/soolsool/acceptance/LiquorCtrAcceptanceTest.java b/src/test/java/com/woowacamp/soolsool/acceptance/LiquorCtrAcceptanceTest.java index 1d3061c4..3f94f50c 100644 --- a/src/test/java/com/woowacamp/soolsool/acceptance/LiquorCtrAcceptanceTest.java +++ b/src/test/java/com/woowacamp/soolsool/acceptance/LiquorCtrAcceptanceTest.java @@ -4,11 +4,14 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import com.woowacamp.soolsool.acceptance.fixture.RestAuthFixture; +import com.woowacamp.soolsool.acceptance.fixture.RestLiquorCtrFixture; import com.woowacamp.soolsool.acceptance.fixture.RestLiquorFixture; import com.woowacamp.soolsool.acceptance.fixture.RestMemberFixture; -import com.woowacamp.soolsool.core.liquor.dto.response.LiquorCtrDetailResponse; +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorCtrDetailResponse; import com.woowacamp.soolsool.core.liquor.infra.RedisLiquorCtr; import io.restassured.RestAssured; +import java.util.List; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -36,14 +39,19 @@ void setUpData() { .put(새로, new RedisLiquorCtr(0L, 0L)); } + @AfterEach + void setOffRedis() { + redissonClient.getMapCache(LIQUOR_CTR_KEY).clear(); + } + @Test @DisplayName("주류 클릭률을 조회한다.") void getLiquorCtr() { /* given */ - RestLiquorFixture.술_목록_조회(); - RestLiquorFixture.술_목록_조회(); - RestLiquorFixture.술_목록_조회(); - RestLiquorFixture.술_상세_조회(새로); + RestLiquorCtrFixture.술_노출수_증가(List.of(새로)); + RestLiquorCtrFixture.술_노출수_증가(List.of(새로)); + RestLiquorCtrFixture.술_노출수_증가(List.of(새로)); + RestLiquorCtrFixture.술_클릭수_증가(새로); /* when */ LiquorCtrDetailResponse response = RestAssured diff --git a/src/test/java/com/woowacamp/soolsool/acceptance/fixture/RestLiquorCtrFixture.java b/src/test/java/com/woowacamp/soolsool/acceptance/fixture/RestLiquorCtrFixture.java new file mode 100644 index 00000000..a3dd3da4 --- /dev/null +++ b/src/test/java/com/woowacamp/soolsool/acceptance/fixture/RestLiquorCtrFixture.java @@ -0,0 +1,37 @@ +package com.woowacamp.soolsool.acceptance.fixture; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorClickAddRequest; +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorImpressionAddRequest; +import io.restassured.RestAssured; +import java.util.List; + +public abstract class RestLiquorCtrFixture extends RestFixture { + + public static void 술_노출수_증가(List liquorIds) { + LiquorImpressionAddRequest request = new LiquorImpressionAddRequest(liquorIds); + + RestAssured + .given().log().all() + .contentType(APPLICATION_JSON_VALUE) + .accept(APPLICATION_JSON_VALUE) + .body(request) + .when().patch("/api/liquor-ctr/impressions") + .then().log().all() + .extract(); + } + + public static void 술_클릭수_증가(Long liquorId) { + LiquorClickAddRequest request = new LiquorClickAddRequest(liquorId); + + RestAssured + .given().log().all() + .contentType(APPLICATION_JSON_VALUE) + .accept(APPLICATION_JSON_VALUE) + .body(request) + .when().patch("/api/liquor-ctr/clicks") + .then().log().all() + .extract(); + } +} diff --git a/src/test/java/com/woowacamp/soolsool/core/liquor/service/LiquorCtrServiceIntegrationTest.java b/src/test/java/com/woowacamp/soolsool/core/liquor/service/LiquorCtrServiceIntegrationTest.java new file mode 100644 index 00000000..9b5873e2 --- /dev/null +++ b/src/test/java/com/woowacamp/soolsool/core/liquor/service/LiquorCtrServiceIntegrationTest.java @@ -0,0 +1,106 @@ +package com.woowacamp.soolsool.core.liquor.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.woowacamp.soolsool.config.RedisTestConfig; +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorClickAddRequest; +import com.woowacamp.soolsool.core.liquor.dto.liquorCtr.LiquorImpressionAddRequest; +import com.woowacamp.soolsool.core.liquor.infra.RedisLiquorCtr; +import com.woowacamp.soolsool.core.liquor.repository.redisson.LiquorCtrRedisRepository; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.jdbc.Sql; + +@DataJpaTest +@Import({LiquorCtrService.class, RedisTestConfig.class, LiquorCtrRedisRepository.class}) +@DisplayName("통합 테스트: LiquorCtrService") +class LiquorCtrServiceIntegrationTest { + + private static final String LIQUOR_CTR_KEY = "LIQUOR_CTR"; + + @Autowired + LiquorCtrService liquorCtrService; + + @Autowired + RedissonClient redissonClient; + + @BeforeEach + @AfterEach + void setRedisLiquorCtr() { + redissonClient.getMapCache(LIQUOR_CTR_KEY).clear(); + } + + @Test + @Sql({"/liquor-type.sql", "/liquor.sql", "/liquor-ctr.sql"}) + @DisplayName("클릭율을 조회한다.") + void getLiquorCtrByByLiquorId() { + // given + long liquorId = 1L; + + long impression = 1L; + long click = 1L; + redissonClient.getMapCache(LIQUOR_CTR_KEY) + .put(liquorId, new RedisLiquorCtr(impression, click)); + + // when + double ctr = liquorCtrService.getLiquorCtrByLiquorId(liquorId); + + // then + assertThat(ctr).isEqualTo(getExpectedCtr(impression, click)); + } + + @Test + @Sql({"/liquor-type.sql", "/liquor.sql", "/liquor-ctr.sql"}) + @DisplayName("노출수를 증가시킨다.") + void increaseImpression() { + // given + long liquorId = 1L; + LiquorImpressionAddRequest request = new LiquorImpressionAddRequest(List.of(liquorId)); + + long impression = 1L; + long click = 1L; + redissonClient.getMapCache(LIQUOR_CTR_KEY) + .put(liquorId, new RedisLiquorCtr(impression, click)); + + // when + liquorCtrService.increaseImpression(request); + + // then + double ctr = liquorCtrService.getLiquorCtrByLiquorId(liquorId); + assertThat(ctr).isEqualTo(getExpectedCtr(impression + 1, click)); + } + + @Test + @Sql({"/liquor-type.sql", "/liquor.sql", "/liquor-ctr.sql"}) + @DisplayName("클릭수를 증가시킨다.") + void increaseClick() { + // given + long liquorId = 1L; + LiquorClickAddRequest request = new LiquorClickAddRequest(liquorId); + + long impression = 2L; + long click = 1L; + redissonClient.getMapCache(LIQUOR_CTR_KEY) + .put(liquorId, new RedisLiquorCtr(impression, click)); + + // when + liquorCtrService.increaseClick(request); + + // then + double ctr = liquorCtrService.getLiquorCtrByLiquorId(liquorId); + assertThat(ctr).isEqualTo(getExpectedCtr(impression, click + 1)); + } + + private double getExpectedCtr(long impression, long click) { + double ratio = (double) click / impression; + + return Math.round(ratio * 100) / 100.0; + } +} diff --git a/src/test/java/com/woowacamp/soolsool/core/liquor/service/LiquorServiceIntegrationTest.java b/src/test/java/com/woowacamp/soolsool/core/liquor/service/LiquorServiceIntegrationTest.java index 4e51f8ad..86b6ec55 100644 --- a/src/test/java/com/woowacamp/soolsool/core/liquor/service/LiquorServiceIntegrationTest.java +++ b/src/test/java/com/woowacamp/soolsool/core/liquor/service/LiquorServiceIntegrationTest.java @@ -5,56 +5,36 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; -import com.woowacamp.soolsool.config.RedisTestConfig; import com.woowacamp.soolsool.core.liquor.dto.LiquorDetailResponse; +import com.woowacamp.soolsool.core.liquor.dto.LiquorElementResponse; import com.woowacamp.soolsool.core.liquor.dto.LiquorModifyRequest; import com.woowacamp.soolsool.core.liquor.dto.LiquorSaveRequest; import com.woowacamp.soolsool.core.liquor.repository.LiquorBrewCache; import com.woowacamp.soolsool.core.liquor.repository.LiquorQueryDslRepository; import com.woowacamp.soolsool.core.liquor.repository.LiquorRegionCache; import com.woowacamp.soolsool.core.liquor.repository.LiquorStatusCache; -import com.woowacamp.soolsool.core.liquor.repository.redisson.LiquorCtrRedisRepository; -import com.woowacamp.soolsool.core.receipt.repository.redisson.ReceiptRedisRepository; import com.woowacamp.soolsool.global.config.MultipleCacheManagerConfig; import com.woowacamp.soolsool.global.config.QuerydslConfig; import com.woowacamp.soolsool.global.exception.SoolSoolException; import java.time.LocalDateTime; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; -import org.springframework.data.domain.PageRequest; import org.springframework.test.context.jdbc.Sql; @DataJpaTest @Import({LiquorService.class, LiquorBrewCache.class, LiquorStatusCache.class, LiquorRegionCache.class, LiquorQueryDslRepository.class, - QuerydslConfig.class, MultipleCacheManagerConfig.class, - RedisTestConfig.class, ReceiptRedisRepository.class, LiquorCtrRedisRepository.class}) + QuerydslConfig.class, MultipleCacheManagerConfig.class}) @DisplayName("통합 테스트: LiquorService") class LiquorServiceIntegrationTest { - private static final String LIQUOR_CTR_KEY = "LIQUOR_CTR"; - @Autowired LiquorService liquorService; - @Autowired - LiquorCtrRedisRepository liquorCtrRedisRepository; - - @Autowired - RedissonClient redissonClient; - - @BeforeEach - @AfterEach - void setRedisLiquorCtr() { - redissonClient.getMapCache(LIQUOR_CTR_KEY).clear(); - } - @Test @Sql({ "/member-type.sql", "/member.sql", @@ -78,40 +58,27 @@ void liquorDetail() { () -> assertThat(response.getImageUrl()).isEqualTo("/soju-url"), () -> assertThat(response.getAlcohol()).isEqualTo(12.0), () -> assertThat(response.getVolume()).isEqualTo(300), - () -> assertThat(response.getStock()).isEqualTo(100), - () -> assertThat(response.getRelatedLiquors()).hasSize(1) + () -> assertThat(response.getStock()).isEqualTo(100) ); } @Test - @Sql({"/liquor-type.sql", "/liquor.sql", "/liquor-ctr.sql"}) - @DisplayName("상품 상세정보를 조회할 경우 클릭율을 증가시킨다.") - void increaseClick() { - // given - long liquorId = 1L; - - // when - liquorService.liquorDetail(liquorId); - - // then - double ctr = liquorCtrRedisRepository.getCtr(liquorId); - assertThat(ctr).isEqualTo(1.0); - } - - @Test - @Sql({"/liquor-type.sql", "/liquor.sql", "/liquor-ctr.sql"}) - @DisplayName("상품 목록을 조회할 경우 클릭율을 증가시킨다.") - void increaseImpression() { - // given - long liquorId = 1L; + @Sql({ + "/member-type.sql", "/member.sql", + "/liquor-type.sql", "/liquor.sql", "/liquor-ctr.sql", + "/receipt-type.sql", "/receipt.sql", + "/order-type.sql", "/order.sql" + }) + @DisplayName("특정 상품과 함께 많이 구매된 상품 목록을 조회한다.") + void liquorPurchasedTogether() { + /* given */ + Long 새로 = 1L; - // when - liquorService.liquorList(null, null, null, null, - PageRequest.of(0, 1), liquorId + 1); + /* when */ + List response = liquorService.liquorPurchasedTogether(새로); - // then - double ctr = liquorCtrRedisRepository.getCtr(liquorId); - assertThat(ctr).isEqualTo(0.33); + /* then */ + assertThat(response).hasSize(1); } @Test