diff --git a/.github/workflows/shimpyoDeploy.yml b/.github/workflows/shimpyoDeploy.yml
index 1e62321e..b3e1f029 100644
--- a/.github/workflows/shimpyoDeploy.yml
+++ b/.github/workflows/shimpyoDeploy.yml
@@ -23,6 +23,7 @@ jobs:
- name: Make application.properties
run: |
+ echo "${{ secrets.DEFAULT_PROPERTIES }}" | base64 --decode > src/main/resources/application.yml
echo "${{ secrets.LOCAL_PROPERTIES }}" | base64 --decode > src/main/resources/application-local.yml
echo "${{ secrets.PROD_PROPERTIES }}" | base64 --decode > src/main/resources/application-prod.yml
echo "${{ secrets.TEST_PROPERTIES }}" | base64 --decode > src/test/resources/application.yml
diff --git a/.gitignore b/.gitignore
index 66ffcb90..ae2f6338 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,7 @@ build/
.settings
.springBeans
.sts4-cache
-bin/
+bin/git
!**/src/main/**/bin/
!**/src/test/**/bin/
@@ -37,7 +37,8 @@ out/
.vscode/
### Application.yaml ###
-**/*.yaml
+**/application*.yaml
+**/application*.yml
### REST Docs Html ###
**/*.html
diff --git a/README.md b/README.md
index 2f640d95..50eef287 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,29 @@
-# Shimpyo_BE : 숙박 예약 서비스
-
-2023-11-20 ~ 2023-12-01
+# Shimpyo_BE : 숙박 정보 조회/검색 및 숙박 예약 시스템
---
-## 📌 목차
+## 0. 목차
-- [멤버](#멤버)
-- [설정](#설정)
-- [설계](#설계)
- - [아키텍처](#아키텍처)
- - [DB 설계](#DB-설계)
- - [API 설계](#API-설계)
-- [CI/CD](#CICD)
-- [API 문서](#API-문서)
+- [1. 프로젝트 설명](#1-프로젝트-설명)
+ - [🧑🏻💻 팀원](#-팀원)
+ - [💁🏻 소개](#-소개)
+ - [🎯 목적](#-목적)
+ - [🔗 배포 링크](#-배포-링크)
+- [2. 설정](#2-설정)
+- [3. 설계](#3-설계)
+ - [📰 아키텍처](#-아키텍처)
+ - [💾 DB 설계](#-DB-설계)
+ - [📡 API 설계](#-API-설계)
+- [4. CI/CD](#4-cicd)
+- [5. 에러 해결](#5-에러-해결)
+- [6. 회고](#6-회고)
+- [7. API 문서](#7-API-문서)
---
-## 멤버
+## 1. 프로젝트 설명
+
+### 🧑🏻💻 팀원
+### 💁🏻 소개
+
+![](src/main/resources/image/main-page.png)
+> 숙박 정보 조회/검색 및 숙박 예약 시스템
+
+### 🎯 목적
+
+- 숙박 시설 제공 및 검색: 사용자가 특정 지역이나 날짜에 필요한 숙박 시설을 쉽게 찾을 수 있도록 정보를 제공합니다.
+- 가용한 숙소 정보 제공: 사용자에게 해당 지역의 여러 숙소 옵션, 가격, 시설, 평가 등 다양한 정보를 제공하여 선택을 돕습니다.
+- 온라인 예약 및 결제: 사용자가 선택한 숙소를 신속하게 예약하고, 온라인으로 결제할 수 있는 편리한 기능을 제공합니다.
+- 평가 제공: 다른 이용자들의 평가를 통해 사용자가 숙소를 선택할 때 도움을 받을 수 있도록 합니다.
+
+### ⏰ 개발 기간
+
+2023-11-20 ~ 2023-12-01
+
+### 🔗 배포 링크
+
+- BE: https://43.202.234.108.nip.io/
+- FE: https://shimpyo.netlify.app/
+
+### 🪄 실행 가이드
+
+1. Clone 받는다.
+
+```shell
+git clone https://github.com/Shimpyo-House/Shimpyo_BE.git
+cd Shimpyo_BE
+```
+
+2. 설정 파일을 작성한다.
+
+> `application.yaml`
+>
+> ```java
+> spring:
+> profiles:
+> active: local
+
+> `application-local.yaml`
+> ```java
+> spring:
+> config:
+> activate:
+> on-profile: local
+> datasource:
+> driver-class-name: org.mariadb.jdbc.Driver
+> url: jdbc:mariadb://localhost:3308/shimpyo?createDatabaseIfNotExist=true&serverTimezone=Asia/Seoul
+> username: ${DB_USERNAME}
+> password: ${DB_PASSWORD}
+> jpa:
+> hibernate:
+> ddl-auto: update
+> show-sql: true
+> properties:
+> hibernate:
+> default_batch_fetch_size: 100
+> data:
+> redis:
+> host: localhost
+> port: 6379
+> jwt:
+> secret: ${JWT_SECRET}
+> access-token-expire-time: ${ACCEESS_TOKEN_EXPIRE_TIME}
+> refresh-token-expire-time: ${REFRESH_TOKEN_EXPIRE_TIME}
+> open-api:
+> service-key: F+zEWUXK0YK5FtuiHBJpOdqfmCV5qNXt+0F5g7X67//VQnlWbQCMUF5UlKPMetolbj9LAbfC0o7+XBe2AyzaWQ==
+> logging:
+> level:
+> root: debug
+
+3. Docker 컨테이너 실행
+
+```shell
+docker-compose up --build -d
+```
+
+4. Run
+
+```shell
+gradle test
+gradle build
+gradle bootJar
+```
+
+5. 테스트 계정
+- EMAIL: test@mail.com
+- PW: qwer1234!!
---
-## 설정
+## 2. 설정
- 자바 버전: 17
- 스프링 버전: 6.0.13
@@ -140,60 +253,213 @@
- Spring Web
- Test Containers
- Json
-- `applicaion-local.yaml`, `application-prod.yaml`, `.env` 파일은 LMS에서 확인하실 수 있습니다!
+- `application.yaml`, `applicaion-local.yaml`, `application-prod.yaml`, `.env` 파일은 LMS에서 확인하실 수
+ 있습니다!
---
-## 설계
+## 3. 설계
-### 아키텍처
+### 📰 아키텍처
> ![](src/main/resources/image/architecture.png)
-### DB 설계
+### 💾 DB 설계
+
`ERD`
> ![](src/main/resources/image/erd.png)
-### API 설계
+### 📡 API 설계
[Spring REST Docs](#API-문서)를 통해 확인하실 수 있습니다.
---
-## CI/CD
+## 4. CI/CD
-### CI
+### 🛠️ CI
> ![](src/main/resources/image/ci.png)
-### CD
+### 🛠️ CD
> ![](src/main/resources/image/cd.png)
---
-## API 문서
+## 5. 에러 해결
+
+### 📌 주요 에러
+
+> #### < Spring REST Docs snippet 에러 >
+> Spring REST Docs 으로 API를 문서화 하기 위해 asciidoc을 사용합니다.
+>
+> 그런데 테스트 코드를 작성하고 빌드를 수행하면 만들어지는 html을 확인해보니, **snippets 파일을 찾지 못 하는 버그**가 발견되었습니다.
+>
+> ![](src/main/resources/image/docs-error.png)
+> ```shell
+> 11월 27, 2023 7:22:00 오후 uri:classloader:/gems/asciidoctor-2.0.10/lib/asciidoctor/reader.rb preprocess_include_directive
+> SEVERE: member-api.adoc: line 15: include file not found: C:/Users/jeong/Desktop/FC/Shimpyo_BE/{snippets}/auth-rest-controller-docs-test/sign-up/http-request.adoc
+> include file not found: C:/Users/jeong/Desktop/FC/Shimpyo_BE/{snippets}/auth-rest-controller-docs-test/sign-up/http-request.adoc :: member-api.adoc :: C:/Users/jeong/Desktop/FC/Shimpyo_BE/C:/Users/jeong/Desktop/FC/Shimpyo_BE/src/docs/asciidoc/member/member-api.adoc:15 (uri:classloader:/gems/asciidoctor-2.0.10/lib/asciidoctor/reader.rb:preprocess_include_directive)
+> ```
+> #### < 에러 해결 >
+> snippets 경로를 index.adoc 파일이 아니라 member-api.adoc 파일에서 지정해보니, 정상적으로 작동하는 것을 확인할 수 있었습니다.
+>
+> ```shell
+> ifndef::snippets[]
+> :snippets: build/generated-snippets
+> endif::[]
+>
+> = Member REST API Docs
+> :doctype: book
+> :icons: font
+> :source-highlighter: highlightjs
+> :toc: left
+> :toclevels: 2
+>
+> ~~ 생략 ~~
+> ```
+
+> #### < Open API Service Key 에러 >
+> Open API 에서 숙박 상품 조회를 하기 위해 요청을 보냈는데, 다음과 같은 응답을 받았습니다.
+>
+> ```shell
+>
+>
+> SEVICE ERROR
+> SERVICE_KEY_IS_NOT_REGISTERED_ERROR
+> 30
+>
+>
+> ```
+> Service Key가 등록되지 않았다는 에러 응답이었습니다.
+>
+> 디코딩 서비스 키를 사용해보기도 하고 인코딩 서비스키를 사용해보기도 했으나 여전히 같은 응답이 돌아왔습니다.
+>
+> #### < 에러 해결 >
+> Service 키를 먼저 인코딩 한 후 Uri 빌드 시 true 옵션을 부여하여 해결했습니다.
+> ```java
+> // ~~ 생략 ~~
+> private String makeBaseSearchUrl() {
+> String STAY_SEARCH_URI = "/searchStay1";
+> return BASE_URL + STAY_SEARCH_URI +
+> "?serviceKey=" + URLEncoder.encode(SERVICE_KEY, StandardCharsets.UTF_8) +
+> DEFAULT_QUERY_PARAMS;
+> }
+> // ~~생략~~
+> private JSONObject getAccommodation(int pageSize, int pageNum) throws JSONException {
+> URI uri = UriComponentsBuilder.fromHttpUrl(makeBaseSearchUrl())
+> .queryParam("pageNo", pageNum)
+> .queryParam("numOfRows", pageSize)
+> .build(true).toUri();
+> ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET,
+> httpEntity, String.class);
+> log.info("숙박 정보 조회");
+> return new JSONObject(response.getBody())
+> .getJSONObject("response")
+> .getJSONObject("body");
+> }
+> // ~~ 생략 ~~
+
+> #### < 상수화 Wrapper Type 에러 >
+> 상수 조건 값을 Wrapper Type으로 선언해 빌드시 롬복 자체 식별이 안되는 에러가 발생했습니다.
+>
+> ![](src/main/resources/image/wrapper-error-1.png)
+>
+> #### < 에러 해결 >
+> 유효성 검증 값의 경우 원시 타입으로 선언되어 있어 원시 타입으로 변경하여 해결했습니다.
+>
+> ![](src/main/resources/image/wrapper-error-2.png)
+>
+
+## 6. 회고
+
+정의정
+
+
+- **느낀 점**
+ - 본 프로젝트에서 BE 팀장으로서 참여하였는데, 전체적인 프로젝트 총괄을 맡으면서 각 팀간 소통과 회의 진행에 있어서 어려움을 느꼈습니다.
+ - 이번 협업 경험을 통해 다른 분야와의 소통 방식에 대해 고민하고, 원활한 소통을 위해 서로의 이해와 배려가 중요하다는 것을 다시 한 번 느낄 수 있었습니다.
+
+- **프로젝트를 하면서 잘했던 점**
+ - 매일 Daliy Scrum을 진행하여 현재 프로젝트 진행 상황을 파악하고 효과적으로 일 단위 task를 수행할 수 있었습니다.
+ - 2주 단위 스프린트로 기능을 개발하여 짧은 시간 안에 효율적인 개발이 가능했습니다.
+ - 적극적인 코드 리뷰를 통해 더 나은 코드 작성법에 대해 생각할 수 있었고, 팀원 간 코드를 분석하고 배울 수 있었습니다.
+ - GitHub와 Discord를 웹훅을 통해 연결하여 PR, ISSUE 등 알림 기능을 구현하여 빠른 피드백이 가능했습니다.
+ - REST Docs를 도입하여 테스트 코드를 통한 안정적인 API 문서화를 수행할 수 있었습니다.
+
+- **프로젝트를 진행하면서 힘들었던 점**
+ - FE와 개발 속도를 맞추는데 어려움을 느꼈습니다.
+ - 본 프로젝트는 FE 5명, BE 3명으로, BE가 비교적 적은 인원이었습니다.
+ - 요구사항과 일정에 맞게 개발을 하면서 FE 팀이 API 연결 기간을 충분히 가질 수 있도록 하기 위해 많은 노력이 필요했습니다.
+ - 또한, 서로의 기술에 대해 모르다보니 FE와의 소통에 어려움을 느꼈습니다.
+ - FE와의 협업을 위한 기본적인 지식을 겸비해야겠다는 생각을 했습니다.
+
+
+
+
+
+이주연
+
+
+- **느낀 점**
+ - 프론트엔드와의 협업 프로젝트는 처음이었는데, 프론트엔드와 소통하며 API를 연결하는 방법을 배울 수 있었던 시간이었습니다.
+ - 매일 10시, 17시 데일리 스크럼 시간을 잘 활용해 서로의 상황을 꾸준히 공유하여 계획에 큰 차질없이 프로젝트가 진행될 수 있었던 것 같습니다.
+ - 백엔드 팀원들간의 코드 리뷰를 통해 서로의 코드를 공유하고 의견을 나누며 많이 배울 수 있는 시간이었습니다.
+
+- **프로젝트를 하면서 잘했던 점**
+ - 매일 10시, 17시 데일리 스크럼 시간을 잘 활용해 서로의 상황을 꾸준히 공유하여 계획에 큰 차질없이 프로젝트가 진행될 수 있었던 것 같습니다.
+
+- **프로젝트를 진행하면서 힘들었던 점**
+ - 프론트엔드에 대해 지식이 부족하기 때문에 프론트엔드가 설명해주는 것을 빠르게 이해하고 소통하는데 어려움을 겪었습니다. 이번 프로젝트를 통해 프론트엔드에 대해 조금 알아갈 수 있었던 것 같습니다.
+
+
+
+
+
+심재철
+
+
+- **배운 점**
+ - 프로젝트를 통해 나의 경험 중 하나로, 효과적인 협업을 위해서는 자신의 진행 상황을 솔직하게, 빠르게 전달하는 것이 상당히 중요하다는 것을 깨달았습니다. 투명하게 소통함으로써 팀 전체가 현재 상황을 정확히 파악하고, 문제 발생 시 신속한 대응이 가능하다는 것을 인지했습니다.
+
+- **프로젝트를 하면서 잘했던 점**
+ - 프로젝트 진행 중에는 효과적인 협업을 실현하기 위해 서로의 진행 상황을 체계적으로 공유하면서 개발을 진행했습니다.
+ - 팀 원 개개인이 빈 시간이 발생할 때 주도적으로 추가 기능이나 프로젝트에 필요한 작업들을 수행하고자 해서 프로젝트 일정에 맞출 수 있었습니다.
+
+- **프로젝트를 진행하면서 힘들었던 점**
+ - 프로젝트에서 가장 어려웠던 부분은 팀 구성원이 3명이어서 요구사항에 따른 API 개발을 프로젝트 일정에 맞추는데 빠듯함이 있었습니다.
+
+
+
+
+## 7. API 문서
※ Spring REST Docs로 문서화했습니다.
> `index`
-> ![](src/main/resources/image/index-docs.png)
+> ![](src/main/resources/image/docs/index-docs.png)
>
> `Member API Docs`
-> ![](src/main/resources/image/member-docs.png)
+> ![](src/main/resources/image/docs/member-docs.png)
>
> `Product API Docs`
-> ![](src/main/resources/image/product-docs.png)
+> ![](src/main/resources/image/docs/product-docs.png)
+>
+> `Room API Docs`
+> ![](src/main/resources/image/docs/room-docs.png)
>
> `Cart API Docs`
-> ![](src/main/resources/image/cart-docs.png)
+> ![](src/main/resources/image/docs/cart-docs.png)
>
> `Reservation API Docs`
-> ![](src/main/resources/image/reservation-docs.png)
+> ![](src/main/resources/image/docs/reservation-docs.png)
>
> `Reservation Product API Docs`
-> ![](src/main/resources/image/reservation-product-docs.png)
+> ![](src/main/resources/image/docs/reservation-product-docs.png)
>
> `Star API Docs`
-> ![](src/main/resources/image/star-docs.png)
->
\ No newline at end of file
+> ![](src/main/resources/image/docs/star-docs.png)
+>
+> `Favorite API Docs`
+> ![](src/main/resources/image/docs/favorite-docs.png)
diff --git a/src/docs/asciidoc/cart/cart-api.adoc b/src/docs/asciidoc/cart/cart-api.adoc
index db9c0f1a..dbac0573 100644
--- a/src/docs/asciidoc/cart/cart-api.adoc
+++ b/src/docs/asciidoc/cart/cart-api.adoc
@@ -2,7 +2,7 @@ ifndef::snippets[]
:snippets: build/generated-snippets
endif::[]
-= Member REST API Docs
+= Cart REST API Docs
:doctype: book
:icons: font
:source-highlighter: highlightjs
diff --git a/src/docs/asciidoc/favorite/favorite-api.adoc b/src/docs/asciidoc/favorite/favorite-api.adoc
new file mode 100644
index 00000000..fedccd13
--- /dev/null
+++ b/src/docs/asciidoc/favorite/favorite-api.adoc
@@ -0,0 +1,52 @@
+ifndef::snippets[]
+:snippets: build/generated-snippets
+endif::[]
+
+= Favorite REST API Docs
+:doctype: book
+:icons: font
+:source-highlighter: highlightjs
+:toc: left
+:toclevels: 2
+
+[[Register]]
+== 즐겨찾기 등록
+
+즐겨찾기 등록 API 입니다.
+
+=== HttpRequest
+
+include::{snippets}/favorite-rest-controller-docs-test/register/http-request.adoc[]
+
+=== HttpResponse
+
+include::{snippets}/favorite-rest-controller-docs-test/register/http-response.adoc[]
+include::{snippets}/favorite-rest-controller-docs-test/register/response-fields.adoc[]
+
+[[Get-Favorites]]
+== 즐겨찾기 목록 조회
+
+즐겨찾기 목록 조회 API 입니다.
+
+=== HttpRequest
+
+include::{snippets}/favorite-rest-controller-docs-test/get-favorites/http-request.adoc[]
+
+=== HttpResponse
+
+include::{snippets}/favorite-rest-controller-docs-test/get-favorites/http-response.adoc[]
+include::{snippets}/favorite-rest-controller-docs-test/get-favorites/response-fields.adoc[]
+
+[[Cancel]]
+== 즐겨찾기 취소
+
+즐겨찾기 취소 API 입니다.
+
+=== HttpRequest
+
+include::{snippets}/favorite-rest-controller-docs-test/cancel/http-request.adoc[]
+
+=== HttpResponse
+
+include::{snippets}/favorite-rest-controller-docs-test/cancel/http-response.adoc[]
+include::{snippets}/favorite-rest-controller-docs-test/cancel/response-fields.adoc[]
diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc
index 19edb242..5a6d322d 100644
--- a/src/docs/asciidoc/index.adoc
+++ b/src/docs/asciidoc/index.adoc
@@ -11,10 +11,20 @@
=== link:member/member-api.html[회원 API, window=blank]
-=== link:product/product-api.html[상품 API, window=blank]
+=== link:product/product-api.html[숙소 API, window=blank]
+
+=== link:room/room-api.html[객실 API, window=blank]
=== link:cart/cart-api.html[장바구니 API, window=blank]
+=== link:reservationproduct/reservation-product-api.html[예약 상품 API, window=blank]
+
+=== link:reservation/reservation-api.html[예약 API, window=blank]
+
+=== link:star/star-api.html[별점 API, window=blank]
+
+=== link:favorite/favorite-api.html[즐겨찾기 API, window=blank]
+
== API Common Response
diff --git a/src/docs/asciidoc/product/product-api.adoc b/src/docs/asciidoc/product/product-api.adoc
index fad29ae1..f2e87703 100644
--- a/src/docs/asciidoc/product/product-api.adoc
+++ b/src/docs/asciidoc/product/product-api.adoc
@@ -2,7 +2,7 @@ ifndef::snippets[]
:snippets: build/generated-snippets
endif::[]
-= Member REST API Docs
+= Product REST API Docs
:doctype: book
:icons: font
:source-highlighter: highlightjs
diff --git a/src/docs/asciidoc/room/room-api.adoc b/src/docs/asciidoc/room/room-api.adoc
new file mode 100644
index 00000000..39559c40
--- /dev/null
+++ b/src/docs/asciidoc/room/room-api.adoc
@@ -0,0 +1,25 @@
+ifndef::snippets[]
+:snippets: build/generated-snippets
+endif::[]
+
+= Room REST API Docs
+:doctype: book
+:icons: font
+:source-highlighter: highlightjs
+:toc: left
+:toclevels: 2
+
+[[Get-Rooms-With-Product-Info]]
+== 숙박 정보를 포함한 객실 정보 리스트 조회
+
+숙박 정보를 포함한 객실 정보 리스트 조회 API 입니다. (주문 페이지에서 필요한 조회)
+
+=== HttpRequest
+
+include::{snippets}/room-rest-controller-docs-test/get-rooms-with-product-info/http-request.adoc[]
+include::{snippets}/room-rest-controller-docs-test/get-rooms-with-product-info/query-parameters.adoc[]
+
+=== HttpResponse
+
+include::{snippets}/room-rest-controller-docs-test/get-rooms-with-product-info/http-response.adoc[]
+include::{snippets}/room-rest-controller-docs-test/get-rooms-with-product-info/response-fields.adoc[]
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/controller/CartRestController.java b/src/main/java/com/fc/shimpyo_be/domain/cart/controller/CartRestController.java
index 7e9f66da..23be1ce3 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/cart/controller/CartRestController.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/controller/CartRestController.java
@@ -13,6 +13,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -25,6 +26,7 @@
@RestController
@RequestMapping("/api/carts")
@RequiredArgsConstructor
+@Transactional(readOnly = true)
public class CartRestController {
private final CartService cartService;
@@ -36,10 +38,9 @@ public ResponseEntity>> getCarts() {
}
@PostMapping
+ @Transactional
public ResponseEntity> addCart(
@Valid @RequestBody CartCreateRequest cartCreateRequest) {
- log.debug("startDate: {}, endDate: {}, price: {}", cartCreateRequest.startDate(),
- cartCreateRequest.endDate(), cartCreateRequest.price());
if (DateTimeUtil.isNotValidDate(DateTimeUtil.toLocalDate(cartCreateRequest.startDate()),
DateTimeUtil.toLocalDate(cartCreateRequest.endDate()))) {
throw new InvalidDateException();
@@ -50,9 +51,9 @@ public ResponseEntity> addCart(
}
@DeleteMapping("/{cartId}")
+ @Transactional
public ResponseEntity> deleteCart(
@PathVariable("cartId") Long cartId) {
- log.debug("cartId: {}", cartId);
return ResponseEntity.ok().body(
ResponseDto.res(HttpStatus.OK, cartService.deleteCart(cartId), "장바구니를 성공적으로 삭제했습니다."));
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/dto/request/CartCreateRequest.java b/src/main/java/com/fc/shimpyo_be/domain/cart/dto/request/CartCreateRequest.java
index 7d775828..03188608 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/cart/dto/request/CartCreateRequest.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/dto/request/CartCreateRequest.java
@@ -5,7 +5,7 @@
import jakarta.validation.constraints.Pattern;
import lombok.Builder;
-public record CartCreateRequest(@NotNull Long roomId,
+public record CartCreateRequest(@NotNull Long roomCode,
@Pattern(regexp = DateTimeUtil.LOCAL_DATE_REGEX_PATTERN, message = "잘못된 시간 형식입니다. (올바른 예시: 2023-10-25)") String startDate,
@Pattern(regexp = DateTimeUtil.LOCAL_DATE_REGEX_PATTERN, message = "잘못된 시간 형식입니다. (올바른 예시: 2023-10-25)") String endDate,
@@ -13,8 +13,8 @@ public record CartCreateRequest(@NotNull Long roomId,
Long price) {
@Builder
- public CartCreateRequest(Long roomId, String startDate, String endDate, Long price) {
- this.roomId = roomId;
+ public CartCreateRequest(Long roomCode, String startDate, String endDate, Long price) {
+ this.roomCode = roomCode;
this.startDate = startDate;
this.endDate = endDate;
this.price = price;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/dto/response/CartDeleteResponse.java b/src/main/java/com/fc/shimpyo_be/domain/cart/dto/response/CartDeleteResponse.java
index ebfd9553..1b65f396 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/cart/dto/response/CartDeleteResponse.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/dto/response/CartDeleteResponse.java
@@ -2,12 +2,12 @@
import lombok.Builder;
-public record CartDeleteResponse(Long cartId, Long roomId, String startDate, String endDate) {
+public record CartDeleteResponse(Long cartId, Long roomCode, String startDate, String endDate) {
@Builder
- public CartDeleteResponse(Long cartId, Long roomId, String startDate, String endDate) {
+ public CartDeleteResponse(Long cartId, Long roomCode, String startDate, String endDate) {
this.cartId = cartId;
- this.roomId = roomId;
+ this.roomCode = roomCode;
this.startDate = startDate;
this.endDate = endDate;
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/dto/response/CartResponse.java b/src/main/java/com/fc/shimpyo_be/domain/cart/dto/response/CartResponse.java
index dc519468..609df20b 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/cart/dto/response/CartResponse.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/dto/response/CartResponse.java
@@ -11,7 +11,7 @@ public class CartResponse {
private final Long productId;
private final String productName;
private final String image;
- private final Long roomId;
+ private final Long roomCode;
private final String roomName;
private final Long price;
private final String description;
@@ -21,21 +21,17 @@ public class CartResponse {
private final String endDate;
private final String checkIn;
private final String checkOut;
- private Boolean reserved = false;
-
- public void setReserved(){
- reserved = true;
- }
@Builder
- public CartResponse(Long cartId, Long productId, String productName, String image, Long roomId,
+ private CartResponse(Long cartId, Long productId, String productName, String image,
+ Long roomCode,
String roomName, Long price, String description, Long standard, Long capacity,
- String startDate, String endDate, String checkIn, String checkOut, Boolean reserved) {
+ String startDate, String endDate, String checkIn, String checkOut) {
this.cartId = cartId;
this.productId = productId;
this.productName = productName;
this.image = image;
- this.roomId = roomId;
+ this.roomCode = roomCode;
this.roomName = roomName;
this.price = price;
this.description = description;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/entity/Cart.java b/src/main/java/com/fc/shimpyo_be/domain/cart/entity/Cart.java
index 0b1d272a..9b3b4a45 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/cart/entity/Cart.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/entity/Cart.java
@@ -1,7 +1,6 @@
package com.fc.shimpyo_be.domain.cart.entity;
import com.fc.shimpyo_be.domain.member.entity.Member;
-import com.fc.shimpyo_be.domain.room.entity.Room;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
@@ -15,6 +14,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@@ -23,28 +23,29 @@ public class Cart {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment(value = "장바구니 식별자")
private Long id;
-
+ @Comment(value = "회원 식별자")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
-
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "room_id", nullable = false)
- private Room room;
+ @Comment(value = "객실 코드")
+ @Column(nullable = false)
+ private Long roomCode;
+ @Comment(value = "총 이용 금액")
@Column(nullable = false)
private Long price;
-
+ @Comment(value = "숙박 시작일")
@Column(nullable = false)
private LocalDate startDate;
-
+ @Comment(value = "숙박 마지막일")
@Column(nullable = false)
private LocalDate endDate;
@Builder
- public Cart(Room room, Member member, Long price, LocalDate startDate, LocalDate endDate) {
- this.room = room;
+ public Cart(Long roomCode, Member member, Long price, LocalDate startDate, LocalDate endDate) {
+ this.roomCode = roomCode;
this.member = member;
this.price = price;
this.startDate = startDate;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartCustomRepository.java b/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartCustomRepository.java
new file mode 100644
index 00000000..71a25423
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartCustomRepository.java
@@ -0,0 +1,9 @@
+package com.fc.shimpyo_be.domain.cart.repository;
+
+import com.fc.shimpyo_be.domain.cart.dto.request.CartCreateRequest;
+import java.time.LocalDate;
+
+public interface CartCustomRepository {
+ Long countByRoomCodeAndMemberIdContainsDate (CartCreateRequest cartCreateRequest, Long memberId);
+}
+
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartCustomRepositoryImpl.java b/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartCustomRepositoryImpl.java
new file mode 100644
index 00000000..dd4adcd2
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartCustomRepositoryImpl.java
@@ -0,0 +1,52 @@
+package com.fc.shimpyo_be.domain.cart.repository;
+
+import static com.fc.shimpyo_be.domain.cart.entity.QCart.cart;
+
+import com.fc.shimpyo_be.domain.cart.dto.request.CartCreateRequest;
+import com.fc.shimpyo_be.global.util.DateTimeUtil;
+import com.querydsl.core.QueryException;
+import com.querydsl.core.types.dsl.BooleanExpression;
+import com.querydsl.core.types.dsl.DateTemplate;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class CartCustomRepositoryImpl implements CartCustomRepository {
+
+ private final JPAQueryFactory queryFactory;
+
+ CartCustomRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
+ this.queryFactory = jpaQueryFactory;
+ }
+
+ @Override
+ public Long countByRoomCodeAndMemberIdContainsDate(CartCreateRequest cartCreateRequest,
+ Long memberId) {
+
+ LocalDate startDate = DateTimeUtil.toLocalDate(cartCreateRequest.startDate());
+ LocalDate endDate = DateTimeUtil.toLocalDate(cartCreateRequest.endDate());
+
+ return queryFactory
+ .selectFrom(cart)
+ .leftJoin(cart.member)
+ .where(buildSearchConditions(cartCreateRequest.roomCode(), memberId,
+ startDate, endDate)).fetchCount();
+ }
+
+ private BooleanExpression buildSearchConditions(Long roomCode, Long memberId,
+ LocalDate startDate, LocalDate endDate) {
+ List expressions = new ArrayList<>();
+
+ if (roomCode == null || memberId == null || startDate == null || endDate == null) {
+ throw new QueryException("잘못된 쿼리 입니다.");
+ }
+
+ expressions.add(cart.member.id.eq(memberId).and(cart.roomCode.eq(roomCode))
+ .and(cart.startDate.before(endDate).and(cart.endDate.after(startDate))));
+
+ return expressions.stream().reduce(BooleanExpression::and).orElse(null);
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartRepository.java b/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartRepository.java
index f717a6b9..81804cc8 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartRepository.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/repository/CartRepository.java
@@ -2,6 +2,7 @@
import com.fc.shimpyo_be.domain.cart.entity.Cart;
+import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -9,4 +10,5 @@
public interface CartRepository extends JpaRepository {
Optional> findByMemberId(Long memberId);
+
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/service/CartService.java b/src/main/java/com/fc/shimpyo_be/domain/cart/service/CartService.java
index 965815ca..a30361b9 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/cart/service/CartService.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/service/CartService.java
@@ -6,12 +6,12 @@
import com.fc.shimpyo_be.domain.cart.entity.Cart;
import com.fc.shimpyo_be.domain.cart.exception.CartNotDeleteException;
import com.fc.shimpyo_be.domain.cart.exception.CartNotFoundException;
+import com.fc.shimpyo_be.domain.cart.repository.CartCustomRepositoryImpl;
import com.fc.shimpyo_be.domain.cart.repository.CartRepository;
import com.fc.shimpyo_be.domain.cart.util.CartMapper;
import com.fc.shimpyo_be.domain.member.entity.Member;
import com.fc.shimpyo_be.domain.member.exception.MemberNotFoundException;
import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
-import com.fc.shimpyo_be.domain.product.exception.RoomNotFoundException;
import com.fc.shimpyo_be.domain.product.exception.RoomNotReserveException;
import com.fc.shimpyo_be.domain.product.service.ProductService;
import com.fc.shimpyo_be.domain.room.entity.Room;
@@ -19,6 +19,7 @@
import com.fc.shimpyo_be.global.util.SecurityUtil;
import jakarta.validation.Valid;
import java.util.List;
+import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -38,15 +39,13 @@ public class CartService {
private final ProductService productService;
+ private final CartCustomRepositoryImpl cartCustomRepository;
+
public List getCarts() {
- List cartResponses= cartRepository.findByMemberId(securityUtil.getCurrentMemberId()).orElseThrow()
- .stream().map(CartMapper::toCartResponse).toList();
- cartResponses.stream().filter(
- cartResponse -> !productService.isAvailableForReservation(cartResponse.getRoomId(),
- cartResponse.getStartDate(), cartResponse.getEndDate())).forEach(
- CartResponse::setReserved);
-
- return cartResponses;
+ List carts = cartRepository.findByMemberId(
+ securityUtil.getCurrentMemberId()).orElseThrow();
+
+ return carts.stream().map(this::getCartResponse).toList();
}
@Transactional
@@ -55,19 +54,24 @@ public CartResponse addCart(@Valid @RequestBody CartCreateRequest cartCreateRequ
Member member = memberRepository.findById(securityUtil.getCurrentMemberId())
.orElseThrow(MemberNotFoundException::new);
- if (!productService.isAvailableForReservation(cartCreateRequest.roomId(),
- cartCreateRequest.startDate(), cartCreateRequest.endDate())) {
+ Long countAvailableForReservation = productService.countAvailableForReservationUsingRoomCode(
+ cartCreateRequest.roomCode(),
+ cartCreateRequest.startDate(),
+ cartCreateRequest.endDate());
+
+ if (countAvailableForReservation <= 0
+ || cartCustomRepository.countByRoomCodeAndMemberIdContainsDate(
+ cartCreateRequest, member.getId()) + 1
+ > countAvailableForReservation) {
throw new RoomNotReserveException();
}
- Room room = roomRepository.findById(cartCreateRequest.roomId())
- .orElseThrow(RoomNotFoundException::new);
- Cart createdCart = cartRepository.save(CartMapper.toCart(cartCreateRequest, room, member));
- return CartMapper.toCartResponse(createdCart);
+ Cart createdCart = cartRepository.save(CartMapper.toCart(cartCreateRequest, member));
+ return getCartResponse(createdCart);
}
@Transactional
- public CartDeleteResponse deleteCart(Long cartId) {
+ public CartDeleteResponse deleteCart(final Long cartId) {
Cart cart = cartRepository.findById(cartId).orElseThrow(CartNotFoundException::new);
if (!cart.getMember().getId().equals(securityUtil.getCurrentMemberId())) {
@@ -78,5 +82,24 @@ public CartDeleteResponse deleteCart(Long cartId) {
return CartMapper.toCartDeleteResponse(cart);
}
+ @Transactional
+ public CartDeleteResponse deleteCart(final Long memberId, final Long cartId) {
+ Cart cart = cartRepository.findById(cartId).orElseThrow(CartNotFoundException::new);
+
+ if (!cart.getMember().getId().equals(memberId)) {
+ throw new CartNotDeleteException();
+ }
+ cartRepository.deleteById(cart.getId());
+
+ return CartMapper.toCartDeleteResponse(cart);
+ }
+
+ private CartResponse getCartResponse(final Cart cart) {
+ List rooms = Optional.of(roomRepository.findByCode(cart.getRoomCode()))
+ .orElseThrow();
+
+ return CartMapper.toCartResponse(cart, rooms.get(0));
+ }
+
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/cart/util/CartMapper.java b/src/main/java/com/fc/shimpyo_be/domain/cart/util/CartMapper.java
index 4a815e46..89658b59 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/cart/util/CartMapper.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/cart/util/CartMapper.java
@@ -11,12 +11,11 @@
public class CartMapper {
- public static CartResponse toCartResponse(Cart cart) {
- Room room = cart.getRoom();
+ public static CartResponse toCartResponse(Cart cart, Room room) {
Product product = room.getProduct();
return CartResponse.builder().cartId(cart.getId()).productId(product.getId())
- .productName(product.getName()).image(product.getThumbnail()).roomId(room.getId())
+ .productName(product.getName()).image(product.getThumbnail()).roomCode(room.getCode())
.roomName(room.getName()).price(cart.getPrice()).description(room.getDescription())
.standard((long) room.getStandard()).capacity((long) room.getCapacity())
.startDate(DateTimeUtil.toString(cart.getStartDate()))
@@ -25,14 +24,14 @@ public static CartResponse toCartResponse(Cart cart) {
}
public static CartDeleteResponse toCartDeleteResponse(Cart cart) {
- return CartDeleteResponse.builder().cartId(cart.getId()).roomId(cart.getRoom().getId())
+ return CartDeleteResponse.builder().cartId(cart.getId()).roomCode(cart.getRoomCode())
.startDate(DateTimeUtil.toString(cart.getStartDate()))
.endDate(DateTimeUtil.toString(cart.getEndDate())).build();
}
- public static Cart toCart(CartCreateRequest cartCreateRequest, Room room, Member member) {
+ public static Cart toCart(CartCreateRequest cartCreateRequest, Member member) {
return Cart.builder()
- .room(room)
+ .roomCode(cartCreateRequest.roomCode())
.member(member)
.price(cartCreateRequest.price())
.startDate(DateTimeUtil.toLocalDate(cartCreateRequest.startDate()))
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/controller/FavoriteRestController.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/controller/FavoriteRestController.java
new file mode 100644
index 00000000..9980ffab
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/controller/FavoriteRestController.java
@@ -0,0 +1,53 @@
+package com.fc.shimpyo_be.domain.favorite.controller;
+
+import com.fc.shimpyo_be.domain.favorite.dto.FavoriteResponseDto;
+import com.fc.shimpyo_be.domain.favorite.dto.FavoritesResponseDto;
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
+import com.fc.shimpyo_be.domain.favorite.service.FavoriteService;
+import com.fc.shimpyo_be.domain.product.util.model.PageableConstraint;
+import com.fc.shimpyo_be.global.common.ResponseDto;
+import com.fc.shimpyo_be.global.util.SecurityUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/favorites")
+public class FavoriteRestController {
+
+ private final FavoriteService favoriteService;
+ private final SecurityUtil securityUtil;
+ private static final int DEFAULT_SIZE = 10;
+ private static final int DEFAULT_PAGE = 0;
+
+ @PostMapping("/{productId}")
+ public ResponseEntity> register(@PathVariable long productId) {
+ return ResponseEntity.status(HttpStatus.CREATED).body(ResponseDto.res(HttpStatus.CREATED,
+ favoriteService.register(securityUtil.getCurrentMemberId(), productId),
+ "성공적으로 즐겨찾기를 등록했습니다."));
+ }
+
+ @GetMapping
+ public ResponseEntity> getFavorites(
+ @PageableConstraint(Favorite.class) @PageableDefault(size = DEFAULT_SIZE, page = DEFAULT_PAGE) Pageable pageable) {
+ return ResponseEntity.status(HttpStatus.OK)
+ .body(ResponseDto.res(HttpStatus.OK, favoriteService.getFavorites(
+ securityUtil.getCurrentMemberId(), pageable), "성공적으로 즐겨찾기 목록을 조회했습니다."));
+ }
+
+ @DeleteMapping("/{productId}")
+ public ResponseEntity> cancel(@PathVariable long productId) {
+ return ResponseEntity.status(HttpStatus.OK).body(ResponseDto.res(HttpStatus.OK,
+ favoriteService.delete(securityUtil.getCurrentMemberId(), productId),
+ "성공적으로 즐겨찾기를 취소했습니다."));
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/dto/FavoriteResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/dto/FavoriteResponseDto.java
new file mode 100644
index 00000000..6594df31
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/dto/FavoriteResponseDto.java
@@ -0,0 +1,31 @@
+package com.fc.shimpyo_be.domain.favorite.dto;
+
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class FavoriteResponseDto {
+
+ private Long favoriteId;
+ private Long memberId;
+ private Long productId;
+
+ @Builder
+ private FavoriteResponseDto(Long favoriteId, Long memberId, Long productId) {
+ this.favoriteId = favoriteId;
+ this.memberId = memberId;
+ this.productId = productId;
+ }
+
+ public static FavoriteResponseDto of(Favorite favorite) {
+ return FavoriteResponseDto.builder()
+ .favoriteId(favorite.getId())
+ .memberId(favorite.getMember().getId())
+ .productId(favorite.getProduct().getId())
+ .build();
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/dto/FavoritesResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/dto/FavoritesResponseDto.java
new file mode 100644
index 00000000..9c45db18
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/dto/FavoritesResponseDto.java
@@ -0,0 +1,22 @@
+package com.fc.shimpyo_be.domain.favorite.dto;
+
+import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class FavoritesResponseDto {
+
+ private int pageCount;
+ private List products;
+
+ @Builder
+ private FavoritesResponseDto(int pageCount, List products) {
+ this.pageCount = pageCount;
+ this.products = products;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/entity/Favorite.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/entity/Favorite.java
index d1d493ea..d4c10bf2 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/favorite/entity/Favorite.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/entity/Favorite.java
@@ -30,7 +30,7 @@ public class Favorite {
private Product product;
@Builder
- public Favorite(Long id, Member member, Product product) {
+ private Favorite(Long id, Member member, Product product) {
this.id = id;
this.member = member;
this.product = product;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/exception/FavoriteAlreadyRegisterException.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/exception/FavoriteAlreadyRegisterException.java
new file mode 100644
index 00000000..5399ec4c
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/exception/FavoriteAlreadyRegisterException.java
@@ -0,0 +1,11 @@
+package com.fc.shimpyo_be.domain.favorite.exception;
+
+import com.fc.shimpyo_be.global.exception.ApplicationException;
+import com.fc.shimpyo_be.global.exception.ErrorCode;
+
+public class FavoriteAlreadyRegisterException extends ApplicationException {
+
+ public FavoriteAlreadyRegisterException() {
+ super(ErrorCode.FAVORITE_ALREADY_REGISTER);
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/exception/FavoriteNotFoundException.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/exception/FavoriteNotFoundException.java
new file mode 100644
index 00000000..886f954a
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/exception/FavoriteNotFoundException.java
@@ -0,0 +1,11 @@
+package com.fc.shimpyo_be.domain.favorite.exception;
+
+import com.fc.shimpyo_be.global.exception.ApplicationException;
+import com.fc.shimpyo_be.global.exception.ErrorCode;
+
+public class FavoriteNotFoundException extends ApplicationException {
+
+ public FavoriteNotFoundException() {
+ super(ErrorCode.FAVORITE_NOT_FOUND);
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteCustomRepository.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteCustomRepository.java
new file mode 100644
index 00000000..c77fec14
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteCustomRepository.java
@@ -0,0 +1,10 @@
+package com.fc.shimpyo_be.domain.favorite.repository;
+
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface FavoriteCustomRepository {
+
+ Page findAllByMemberId(long memberId, Pageable pageable);
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteCustomRepositoryImpl.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteCustomRepositoryImpl.java
new file mode 100644
index 00000000..766b6087
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteCustomRepositoryImpl.java
@@ -0,0 +1,55 @@
+package com.fc.shimpyo_be.domain.favorite.repository;
+
+import static com.fc.shimpyo_be.domain.favorite.entity.QFavorite.favorite;
+import static com.fc.shimpyo_be.domain.member.entity.QMember.member;
+import static com.fc.shimpyo_be.domain.product.entity.QProduct.product;
+
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
+import com.fc.shimpyo_be.global.util.QueryDslUtil;
+import com.querydsl.core.types.Order;
+import com.querydsl.core.types.OrderSpecifier;
+import com.querydsl.jpa.impl.JPAQuery;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import java.util.LinkedList;
+import java.util.List;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.support.PageableExecutionUtils;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class FavoriteCustomRepositoryImpl implements FavoriteCustomRepository {
+
+ private final JPAQueryFactory queryFactory;
+
+ FavoriteCustomRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
+ this.queryFactory = jpaQueryFactory;
+ }
+
+ @Override
+ public Page findAllByMemberId(long memberId, Pageable pageable) {
+ JPAQuery query = queryFactory
+ .selectDistinct(favorite)
+ .from(favorite)
+ .leftJoin(favorite.member, member)
+ .leftJoin(favorite.product, product)
+ .where(member.id.eq(memberId))
+ .offset(pageable.getOffset())
+ .orderBy(getAllOrderSpecifiers(pageable).toArray(OrderSpecifier[]::new))
+ .limit(pageable.getPageSize());
+ JPAQuery countQuery = queryFactory
+ .selectDistinct(favorite)
+ .from(favorite)
+ .leftJoin(favorite.member, member)
+ .leftJoin(favorite.product, product)
+ .where(member.id.eq(memberId));
+ List content = query.fetch();
+ return PageableExecutionUtils.getPage(content, pageable, () -> countQuery.fetch().size());
+ }
+
+ private List> getAllOrderSpecifiers(Pageable pageable) {
+ List> ORDERS = new LinkedList<>();
+ ORDERS.add(QueryDslUtil.getSortedColumn(Order.DESC, favorite, "id"));
+ return ORDERS;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteRepository.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteRepository.java
index a4cbaa1e..9b06b4bb 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteRepository.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/repository/FavoriteRepository.java
@@ -1,8 +1,13 @@
package com.fc.shimpyo_be.domain.favorite.repository;
import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
+import com.fc.shimpyo_be.domain.member.entity.Member;
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
-public interface FavoriteRepository extends JpaRepository {
+public interface FavoriteRepository extends JpaRepository,
+ FavoriteCustomRepository {
+ Optional findByMemberAndProduct(Member member, Product product);
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/favorite/service/FavoriteService.java b/src/main/java/com/fc/shimpyo_be/domain/favorite/service/FavoriteService.java
new file mode 100644
index 00000000..cd4986c8
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/favorite/service/FavoriteService.java
@@ -0,0 +1,69 @@
+package com.fc.shimpyo_be.domain.favorite.service;
+
+import com.fc.shimpyo_be.domain.favorite.dto.FavoriteResponseDto;
+import com.fc.shimpyo_be.domain.favorite.dto.FavoritesResponseDto;
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
+import com.fc.shimpyo_be.domain.favorite.exception.FavoriteAlreadyRegisterException;
+import com.fc.shimpyo_be.domain.favorite.exception.FavoriteNotFoundException;
+import com.fc.shimpyo_be.domain.favorite.repository.FavoriteRepository;
+import com.fc.shimpyo_be.domain.member.entity.Member;
+import com.fc.shimpyo_be.domain.member.service.MemberService;
+import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import com.fc.shimpyo_be.domain.product.exception.ProductNotFoundException;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
+import com.fc.shimpyo_be.domain.product.util.ProductMapper;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class FavoriteService {
+
+ private final FavoriteRepository favoriteRepository;
+ private final MemberService memberService;
+ private final ProductRepository productRepository;
+
+ public FavoriteResponseDto register(long memberId, long productId) {
+ Member member = memberService.getMemberById(memberId);
+ Product product = productRepository.findById(productId).orElseThrow(
+ ProductNotFoundException::new);
+ Optional favorite = favoriteRepository.findByMemberAndProduct(member, product);
+ if (favorite.isPresent()) {
+ throw new FavoriteAlreadyRegisterException();
+ }
+ return FavoriteResponseDto.of(favoriteRepository.save(Favorite.builder()
+ .member(member)
+ .product(product)
+ .build()));
+ }
+
+ public FavoritesResponseDto getFavorites(long memberId, Pageable pageable) {
+ List productResponses = new ArrayList<>();
+ Member member = memberService.getMemberById(memberId);
+ Page favorites = favoriteRepository.findAllByMemberId(member.getId(), pageable);
+ for (Favorite favorite : favorites) {
+ productResponses.add(ProductMapper.toProductResponse(favorite.getProduct(),true));
+ }
+ return FavoritesResponseDto.builder()
+ .pageCount(favorites.getTotalPages())
+ .products(productResponses)
+ .build();
+ }
+
+ public FavoriteResponseDto delete(long memberId, long productId) {
+ Member member = memberService.getMemberById(memberId);
+ Product product = productRepository.findById(productId).orElseThrow(
+ ProductNotFoundException::new);
+ Favorite favorite = favoriteRepository.findByMemberAndProduct(member, product)
+ .orElseThrow(FavoriteNotFoundException::new);
+ FavoriteResponseDto favoriteResponseDto = FavoriteResponseDto.of(favorite);
+ favoriteRepository.delete(favorite);
+ return favoriteResponseDto;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/controller/AuthRestController.java b/src/main/java/com/fc/shimpyo_be/domain/member/controller/AuthRestController.java
index 07744978..d62db927 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/controller/AuthRestController.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/controller/AuthRestController.java
@@ -9,7 +9,6 @@
import com.fc.shimpyo_be.global.common.ResponseDto;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
@@ -17,7 +16,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/auth")
@@ -28,7 +26,6 @@ public class AuthRestController {
@PostMapping("/signup")
public ResponseEntity> signUp(
@Valid @RequestBody SignUpRequestDto signUpRequestDto) {
- log.debug("email: {}, name: {}", signUpRequestDto.getEmail(), signUpRequestDto.getName());
return ResponseEntity.status(HttpStatus.CREATED).body(
ResponseDto.res(HttpStatus.CREATED, authService.signUp(signUpRequestDto),
"성공적으로 회원가입 했습니다."));
@@ -37,7 +34,6 @@ public ResponseEntity> signUp(
@PostMapping("/signin")
public ResponseEntity> signIn(
@Valid @RequestBody SignInRequestDto signInRequestDto) {
- log.debug("email: {}", signInRequestDto.getEmail());
return ResponseEntity.status(HttpStatus.OK).body(
ResponseDto.res(HttpStatus.OK, authService.signIn(signInRequestDto),
"성공적으로 로그인 했습니다."));
@@ -46,8 +42,6 @@ public ResponseEntity> signIn(
@PostMapping("/refresh")
public ResponseEntity> refresh(
@Valid @RequestBody RefreshRequestDto refreshRequestDto) {
- log.debug("accessToken: {}, refreshToken: {}", refreshRequestDto.getAccessToken(),
- refreshRequestDto.getRefreshToken());
return ResponseEntity.status(HttpStatus.OK).body(
ResponseDto.res(HttpStatus.OK, authService.refresh(refreshRequestDto),
"성공적으로 토큰을 재발급 했습니다."));
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/controller/MemberRestController.java b/src/main/java/com/fc/shimpyo_be/domain/member/controller/MemberRestController.java
index 5393e4eb..629d2e16 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/controller/MemberRestController.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/controller/MemberRestController.java
@@ -7,7 +7,6 @@
import com.fc.shimpyo_be.global.common.ResponseDto;
import com.fc.shimpyo_be.global.util.SecurityUtil;
import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@@ -17,7 +16,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/members")
@@ -28,7 +26,6 @@ public class MemberRestController {
@GetMapping
public ResponseEntity> getMember() {
- log.debug("memberId: {}", securityUtil.getCurrentMemberId());
return ResponseEntity.status(HttpStatus.OK).body(
ResponseDto.res(HttpStatus.OK, memberService.getMember(),
"성공적으로 회원 정보를 조회했습니다."));
@@ -37,7 +34,6 @@ public ResponseEntity> getMember() {
@PatchMapping
public ResponseEntity> updateMember(@RequestBody
UpdateMemberRequestDto updateMemberRequestDto) {
- log.debug("memberId: {}", securityUtil.getCurrentMemberId());
return ResponseEntity.status(HttpStatus.OK).body(
ResponseDto.res(HttpStatus.OK, memberService.updateMember(updateMemberRequestDto),
"성공적으로 회원 정보를 수정했습니다."));
@@ -46,7 +42,6 @@ public ResponseEntity> updateMember(@RequestBody
@PostMapping
public ResponseEntity> checkPassword(
@RequestBody CheckPasswordRequestDto checkPasswordRequestDto) {
- log.debug("memberId: {}", securityUtil.getCurrentMemberId());
memberService.checkPassword(checkPasswordRequestDto);
return ResponseEntity.status(HttpStatus.OK).body(
ResponseDto.res(HttpStatus.OK, "비밀번호가 일치합니다."));
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/CheckPasswordRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/CheckPasswordRequestDto.java
index af1324bc..0bfafe93 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/CheckPasswordRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/CheckPasswordRequestDto.java
@@ -1,19 +1,20 @@
package com.fc.shimpyo_be.domain.member.dto.request;
import jakarta.validation.constraints.NotBlank;
+import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CheckPasswordRequestDto {
@NotBlank(message = "비밀번호를 입력하세요.")
private String password;
@Builder
- public CheckPasswordRequestDto(String password) {
+ private CheckPasswordRequestDto(String password) {
this.password = password;
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/RefreshRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/RefreshRequestDto.java
index 3523d5d6..740aecb6 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/RefreshRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/RefreshRequestDto.java
@@ -1,12 +1,13 @@
package com.fc.shimpyo_be.domain.member.dto.request;
import jakarta.validation.constraints.NotBlank;
+import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RefreshRequestDto {
@NotBlank(message = "Access Token 을 입력하세요.")
@@ -15,7 +16,7 @@ public class RefreshRequestDto {
private String refreshToken;
@Builder
- public RefreshRequestDto(String accessToken, String refreshToken) {
+ private RefreshRequestDto(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/SignInRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/SignInRequestDto.java
index ea765cdf..4be9b0e6 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/SignInRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/SignInRequestDto.java
@@ -2,14 +2,14 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
-import jakarta.validation.constraints.Size;
+import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@Getter
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SignInRequestDto {
@NotBlank(message = "이메일을 입력하세요.")
@@ -19,12 +19,12 @@ public class SignInRequestDto {
private String password;
@Builder
- public SignInRequestDto(String email, String password) {
+ private SignInRequestDto(String email, String password) {
this.email = email;
this.password = password;
}
- public UsernamePasswordAuthenticationToken toAuthentication(){
+ public UsernamePasswordAuthenticationToken toAuthentication() {
return new UsernamePasswordAuthenticationToken(this.email, this.password);
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/SignUpRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/SignUpRequestDto.java
index 30dedcd0..ed0425f2 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/SignUpRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/SignUpRequestDto.java
@@ -5,28 +5,36 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
+import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
@Getter
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SignUpRequestDto {
+ private static final String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
+ private static final int NAME_MIN = 2;
+ private static final int NAME_MAX = 30;
+ private static final String PASSWORD_REGEX = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$#^()!%*?&])[A-Za-z\\d@$!#^()%*?&]{8,30}$";
+
@NotBlank(message = "이메일을 입력하세요.")
- @Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "이메일 형식에 맞게 입력해주세요.")
+ @Pattern(regexp = EMAIL_REGEX, message = "이메일 형식에 맞게 입력해주세요.")
private String email;
@NotBlank(message = "이름을 입력하세요.")
- @Size(min = 2, max = 30, message = "이름은 최소 2자 이상 최대 30자 이내로 입력하세요.")
+ @Size(min = NAME_MIN, max = NAME_MAX, message = "이름은 최소 2자 이상 최대 30자 이내로 입력하세요.")
private String name;
@NotBlank(message = "비밀번호를 입력하세요.")
+ @Pattern(regexp = PASSWORD_REGEX)
private String password;
@NotBlank(message = "비밀번호 확인을 입력하세요.")
+ @Pattern(regexp = PASSWORD_REGEX)
private String passwordConfirm;
@Builder
- public SignUpRequestDto(String email, String name, String password, String passwordConfirm) {
+ private SignUpRequestDto(String email, String name, String password, String passwordConfirm) {
this.email = email;
this.name = name;
this.password = password;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/UpdateMemberRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/UpdateMemberRequestDto.java
index 0bef7927..445f874f 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/UpdateMemberRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/dto/request/UpdateMemberRequestDto.java
@@ -1,11 +1,12 @@
package com.fc.shimpyo_be.domain.member.dto.request;
+import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UpdateMemberRequestDto {
private String password;
@@ -13,7 +14,7 @@ public class UpdateMemberRequestDto {
private String photoUrl;
@Builder
- public UpdateMemberRequestDto(String password, String passwordConfirm, String photoUrl) {
+ private UpdateMemberRequestDto(String password, String passwordConfirm, String photoUrl) {
this.password = password;
this.passwordConfirm = passwordConfirm;
this.photoUrl = photoUrl;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/MemberResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/MemberResponseDto.java
index 949a10c4..aa80ac89 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/MemberResponseDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/MemberResponseDto.java
@@ -1,12 +1,13 @@
package com.fc.shimpyo_be.domain.member.dto.response;
import com.fc.shimpyo_be.domain.member.entity.Member;
+import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MemberResponseDto {
private Long memberId;
@@ -15,7 +16,7 @@ public class MemberResponseDto {
private String photoUrl;
@Builder
- public MemberResponseDto(Long memberId, String email, String name, String photoUrl) {
+ private MemberResponseDto(Long memberId, String email, String name, String photoUrl) {
this.memberId = memberId;
this.email = email;
this.name = name;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/SignInResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/SignInResponseDto.java
index 89fd6040..352c70cd 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/SignInResponseDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/SignInResponseDto.java
@@ -1,18 +1,19 @@
package com.fc.shimpyo_be.domain.member.dto.response;
+import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SignInResponseDto {
private MemberResponseDto member;
private TokenResponseDto token;
@Builder
- public SignInResponseDto(MemberResponseDto member, TokenResponseDto token) {
+ private SignInResponseDto(MemberResponseDto member, TokenResponseDto token) {
this.member = member;
this.token = token;
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/TokenResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/TokenResponseDto.java
index 582a440b..7d5ce9f5 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/TokenResponseDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/dto/response/TokenResponseDto.java
@@ -1,11 +1,12 @@
package com.fc.shimpyo_be.domain.member.dto.response;
+import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TokenResponseDto {
private String grantType;
@@ -14,7 +15,7 @@ public class TokenResponseDto {
private String refreshToken;
@Builder
- public TokenResponseDto(String grantType, String accessToken, long accessTokenExpiresIn,
+ private TokenResponseDto(String grantType, String accessToken, long accessTokenExpiresIn,
String refreshToken) {
this.grantType = grantType;
this.accessToken = accessToken;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/entity/Member.java b/src/main/java/com/fc/shimpyo_be/domain/member/entity/Member.java
index 6a621584..94917b07 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/entity/Member.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/entity/Member.java
@@ -13,6 +13,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@@ -21,20 +22,26 @@ public class Member extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("회원 식별자")
private Long id;
@Column(unique = true, nullable = false, length = 30)
+ @Comment("회원 이메일")
private String email;
@Column(nullable = false, length = 30)
+ @Comment("회원 이름")
private String name;
@Column(nullable = false)
+ @Comment("암호화된 비밀번호")
private String password;
@Column(nullable = false, columnDefinition = "TEXT")
+ @Comment("프로필 사진 URL")
private String photoUrl;
@Enumerated(EnumType.STRING)
+ @Comment("권한")
private Authority authority;
@Builder
- public Member(Long id, String email, String name, String password, String photoUrl,
+ private Member(Long id, String email, String name, String password, String photoUrl,
Authority authority) {
this.id = id;
this.email = email;
@@ -50,7 +57,7 @@ public void update(UpdateMemberRequestDto updateMemberRequestDto) {
}
}
- public void changePassword(String password){
+ public void changePassword(String password) {
this.password = password;
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/member/entity/RefreshToken.java b/src/main/java/com/fc/shimpyo_be/domain/member/entity/RefreshToken.java
index fd6d16fb..211d0864 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/member/entity/RefreshToken.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/member/entity/RefreshToken.java
@@ -14,12 +14,13 @@
public class RefreshToken {
@Id
- @Comment("member_id")
+ @Comment("Refresh 토큰 식별자(회원 식별자)")
private Long id;
+ @Comment("Refresh 토큰")
private String token;
@Builder
- public RefreshToken(Long id, String token) {
+ private RefreshToken(Long id, String token) {
this.id = id;
this.token = token;
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/controller/ProductRestController.java b/src/main/java/com/fc/shimpyo_be/domain/product/controller/ProductRestController.java
index ea18dac5..ddf866a2 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/controller/ProductRestController.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/controller/ProductRestController.java
@@ -1,6 +1,7 @@
package com.fc.shimpyo_be.domain.product.controller;
import com.fc.shimpyo_be.domain.product.dto.request.SearchKeywordRequest;
+import com.fc.shimpyo_be.domain.product.dto.response.PaginatedProductResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductDetailsResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
import com.fc.shimpyo_be.domain.product.entity.Product;
@@ -18,6 +19,7 @@
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -30,39 +32,39 @@
@RequestMapping("/api/products")
@RequiredArgsConstructor
@Validated
+@Transactional(readOnly = true)
public class ProductRestController {
private final ProductService productService;
@GetMapping
- public ResponseEntity>> getProducts(
- @RequestParam(required = false) String productName,
- @RequestParam(required = false) String address,
- @RequestParam(required = false) String category,
+ public ResponseEntity> getProducts(
+ @RequestParam(required = false, defaultValue = "") String productName,
+ @RequestParam(required = false, defaultValue = "") String address,
+ @RequestParam(required = false, defaultValue = "") String category,
+ @RequestParam(required = false, defaultValue = "0") Long capacity,
@PageableConstraint(Product.class) @PageableDefault(size = 10, page = 0) Pageable pageable) {
- log.debug("productName: {}, address: {}, category: {}", productName, address, category);
SearchKeywordRequest searchKeywordRequest = SearchKeywordRequest.builder()
- .productName(productName).address(address).category(category).build();
+ .productName(productName).address(address).category(category).capacity(capacity)
+ .build();
return ResponseEntity.ok(ResponseDto.res(HttpStatus.OK,
- productService.getProducts(searchKeywordRequest, pageable), "상품 목록을 성공적으로 조회했습니다."));
+ productService.getProducts(searchKeywordRequest, pageable), "숙소 목록을 성공적으로 조회했습니다."));
}
-
@GetMapping("/{productId}")
public ResponseEntity> getProductDetails(
@PathVariable("productId") Long productId,
@RequestParam @Pattern(regexp = DateTimeUtil.LOCAL_DATE_REGEX_PATTERN, message = "잘못된 시간 형식입니다. (올바른 예시: 2023-10-25)") String startDate,
@RequestParam @Pattern(regexp = DateTimeUtil.LOCAL_DATE_REGEX_PATTERN, message = "잘못된 시간 형식입니다. (올바른 예시: 2023-10-25)") String endDate) {
- log.debug("productId: {}, startDate: {}, endDate: {}", productId, startDate, endDate);
if (DateTimeUtil.isNotValidDate(DateTimeUtil.toLocalDate(startDate),
DateTimeUtil.toLocalDate(endDate))) {
throw new InvalidDateException();
}
return ResponseEntity.ok(ResponseDto.res(HttpStatus.OK,
- productService.getProductDetails(productId, startDate, endDate), "상품을 성공적으로 조회했습니다."));
+ productService.getProductDetails(productId, startDate, endDate), "숙소을 성공적으로 조회했습니다."));
}
@GetMapping("/amounts/{roomId}")
@@ -70,7 +72,6 @@ public ResponseEntity> isAvailableForReservation(
@PathVariable("roomId") Long roomId,
@RequestParam @Pattern(regexp = DateTimeUtil.LOCAL_DATE_REGEX_PATTERN, message = "잘못된 시간 형식입니다. (올바른 예시: 2023-10-25)") String startDate,
@RequestParam @Pattern(regexp = DateTimeUtil.LOCAL_DATE_REGEX_PATTERN, message = "잘못된 시간 형식입니다. (올바른 예시: 2023-10-25)") String endDate) {
- log.debug("roomId: {}, startDate: {}, endDate: {}", roomId, startDate, endDate);
if (DateTimeUtil.isNotValidDate(DateTimeUtil.toLocalDate(startDate),
DateTimeUtil.toLocalDate(endDate))) {
throw new InvalidDateException();
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/dto/request/SearchKeywordRequest.java b/src/main/java/com/fc/shimpyo_be/domain/product/dto/request/SearchKeywordRequest.java
index f237a0d3..ce530603 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/dto/request/SearchKeywordRequest.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/dto/request/SearchKeywordRequest.java
@@ -1,13 +1,27 @@
package com.fc.shimpyo_be.domain.product.dto.request;
+import com.fc.shimpyo_be.domain.product.entity.Category;
+import jakarta.validation.constraints.NotNull;
+import java.util.Arrays;
+import java.util.List;
import lombok.Builder;
-public record SearchKeywordRequest(String productName, String address, String category) {
+public record SearchKeywordRequest(
+ @NotNull
+ String productName,
+ @NotNull
+ String address,
+ @NotNull
+ List category,
+ @NotNull
+ Long capacity) {
+
+ final static List allCategories = Arrays.stream(Category.values()).toList();
@Builder
- public SearchKeywordRequest(String productName, String address, String category) {
- this.productName = productName;
- this.address = address;
- this.category = category;
+ public SearchKeywordRequest(String productName, String address, String category,
+ Long capacity) {
+ this(productName, address,
+ category.equals("") ? allCategories : List.of(Category.getByName(category)), capacity);
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/PaginatedProductResponse.java b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/PaginatedProductResponse.java
new file mode 100644
index 00000000..f328dfdd
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/PaginatedProductResponse.java
@@ -0,0 +1,23 @@
+package com.fc.shimpyo_be.domain.product.dto.response;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Builder;
+
+public record PaginatedProductResponse(
+
+ List productResponses,
+ int pageCount
+) {
+
+ @Builder
+ public PaginatedProductResponse(List productResponses, int pageCount) {
+ if (productResponses != null) {
+ this.productResponses = productResponses;
+ } else {
+ this.productResponses = new ArrayList<>();
+ }
+ this.pageCount = pageCount;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductAddressResponse.java b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductAddressResponse.java
new file mode 100644
index 00000000..68b2586a
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductAddressResponse.java
@@ -0,0 +1,19 @@
+package com.fc.shimpyo_be.domain.product.dto.response;
+
+import lombok.Builder;
+
+public record ProductAddressResponse (
+ String address,
+ String detailAddress,
+ double mapX,
+ double mapY
+) {
+
+ @Builder
+ public ProductAddressResponse(String address, String detailAddress, double mapX, double mapY) {
+ this.address = address;
+ this.detailAddress = detailAddress;
+ this.mapX = mapX;
+ this.mapY = mapY;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductAmenityResponse.java b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductAmenityResponse.java
new file mode 100644
index 00000000..c349176f
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductAmenityResponse.java
@@ -0,0 +1,38 @@
+package com.fc.shimpyo_be.domain.product.dto.response;
+
+import lombok.Builder;
+
+public record ProductAmenityResponse(
+
+ boolean barbecue,
+ boolean beauty,
+ boolean beverage,
+ boolean bicycle,
+ boolean campfire,
+ boolean fitness,
+ boolean karaoke,
+ boolean publicBath,
+ boolean publicPc,
+ boolean sauna,
+ boolean sports,
+ boolean seminar
+) {
+
+ @Builder
+ public ProductAmenityResponse(boolean barbecue, boolean beauty, boolean beverage, boolean bicycle,
+ boolean campfire, boolean fitness, boolean karaoke, boolean publicBath, boolean publicPc,
+ boolean sauna, boolean sports, boolean seminar) {
+ this.barbecue = barbecue;
+ this.beauty = beauty;
+ this.beverage = beverage;
+ this.bicycle = bicycle;
+ this.campfire = campfire;
+ this.fitness = fitness;
+ this.karaoke = karaoke;
+ this.publicBath = publicBath;
+ this.publicPc = publicPc;
+ this.sauna = sauna;
+ this.sports = sports;
+ this.seminar = seminar;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductDetailsResponse.java b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductDetailsResponse.java
index fbc65487..689df9bd 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductDetailsResponse.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductDetailsResponse.java
@@ -1,21 +1,25 @@
package com.fc.shimpyo_be.domain.product.dto.response;
import com.fc.shimpyo_be.domain.room.dto.response.RoomResponse;
+import java.util.ArrayList;
import java.util.List;
import lombok.Builder;
-public record ProductDetailsResponse(Long productId, String category, String address,
- String productName, String description, Boolean favorites,
- Float starAvg,
-
+public record ProductDetailsResponse(Long productId, String category,
+ ProductAddressResponse address,
+ String productName,
+ String description, Boolean favorites, Float starAvg,
List images,
-
+ ProductAmenityResponse productAmenityResponse,
+ ProductOptionResponse productOptionResponse,
List rooms) {
+
@Builder
- public ProductDetailsResponse(Long productId, String category, String address,
+ public ProductDetailsResponse(Long productId, String category, ProductAddressResponse address,
String productName, String description, Boolean favorites, Float starAvg,
- List images, List rooms) {
+ List images, ProductAmenityResponse productAmenityResponse,
+ ProductOptionResponse productOptionResponse, List rooms) {
this.productId = productId;
this.category = category;
this.address = address;
@@ -23,7 +27,13 @@ public ProductDetailsResponse(Long productId, String category, String address,
this.description = description;
this.favorites = favorites;
this.starAvg = starAvg;
- this.images = images;
+ if (images == null) {
+ this.images = new ArrayList<>();
+ } else {
+ this.images = images;
+ }
+ this.productAmenityResponse = productAmenityResponse;
+ this.productOptionResponse = productOptionResponse;
this.rooms = rooms;
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductOptionResponse.java b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductOptionResponse.java
new file mode 100644
index 00000000..0891341a
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/dto/response/ProductOptionResponse.java
@@ -0,0 +1,32 @@
+package com.fc.shimpyo_be.domain.product.dto.response;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+public record ProductOptionResponse(
+ boolean cooking,
+ boolean parking,
+ boolean pickup,
+ String foodPlace,
+ String infoCenter
+
+) {
+ @Builder
+ public ProductOptionResponse(boolean cooking, boolean parking, boolean pickup,
+ String foodPlace,
+ String infoCenter) {
+ this.cooking = cooking;
+ this.parking = parking;
+ this.pickup = pickup;
+ this.foodPlace = foodPlace;
+ this.infoCenter = infoCenter;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/entity/Address.java b/src/main/java/com/fc/shimpyo_be/domain/product/entity/Address.java
new file mode 100644
index 00000000..5ba7bdee
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/entity/Address.java
@@ -0,0 +1,44 @@
+package com.fc.shimpyo_be.domain.product.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Entity
+public class Address {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("숙소 위치 식별자")
+ private Long id;
+ @Column(nullable = false)
+ @Comment("숙소 주소")
+ private String address;
+ @Column(nullable = false)
+ @Comment("숙소 상세 주소")
+ private String detailAddress;
+ @Column(nullable = false)
+ @Comment("숙소 X좌표")
+ private double mapX;
+ @Column(nullable = false)
+ @Comment("숙소 Y좌표")
+ private double mapY;
+
+ @Builder
+ public Address(Long id, String address, String detailAddress, double mapX, double mapY) {
+ this.id = id;
+ this.address = address;
+ this.detailAddress = detailAddress;
+ this.mapX = mapX;
+ this.mapY = mapY;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/entity/Amenity.java b/src/main/java/com/fc/shimpyo_be/domain/product/entity/Amenity.java
new file mode 100644
index 00000000..40c0f9ad
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/entity/Amenity.java
@@ -0,0 +1,78 @@
+package com.fc.shimpyo_be.domain.product.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Entity
+public class Amenity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("숙소 부대시설 식별자")
+ private Long id;
+ @Column(nullable = false)
+ @Comment("바비큐장 여부")
+ private boolean barbecue;
+ @Column(nullable = false)
+ @Comment("뷰티시설 여부")
+ private boolean beauty;
+ @Column(nullable = false)
+ @Comment("식음료장 여부")
+ private boolean beverage;
+ @Column(nullable = false)
+ @Comment("자전거 대여 여부")
+ private boolean bicycle;
+ @Column(nullable = false)
+ @Comment("캠프파이어 여부")
+ private boolean campfire;
+ @Column(nullable = false)
+ @Comment("휘트니스 센터 여부")
+ private boolean fitness;
+ @Column(nullable = false)
+ @Comment("노래방 여부")
+ private boolean karaoke;
+ @Column(nullable = false)
+ @Comment("공동 샤워실 여부")
+ private boolean publicBath;
+ @Column(nullable = false)
+ @Comment("공동 PC실 여부")
+ private boolean publicPc;
+ @Column(nullable = false)
+ @Comment("사우나 여부")
+ private boolean sauna;
+ @Column(nullable = false)
+ @Comment("스포츠 시설 여부")
+ private boolean sports;
+ @Column(nullable = false)
+ @Comment("세미나실 여부")
+ private boolean seminar;
+
+ @Builder
+ public Amenity(Long id, boolean barbecue, boolean beauty, boolean beverage, boolean bicycle,
+ boolean campfire, boolean fitness, boolean karaoke, boolean publicBath, boolean publicPc,
+ boolean sauna, boolean sports, boolean seminar) {
+ this.id = id;
+ this.barbecue = barbecue;
+ this.beauty = beauty;
+ this.beverage = beverage;
+ this.bicycle = bicycle;
+ this.campfire = campfire;
+ this.fitness = fitness;
+ this.karaoke = karaoke;
+ this.publicBath = publicBath;
+ this.publicPc = publicPc;
+ this.sauna = sauna;
+ this.sports = sports;
+ this.seminar = seminar;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/entity/Product.java b/src/main/java/com/fc/shimpyo_be/domain/product/entity/Product.java
index 05b00d2b..f0b332a4 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/entity/Product.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/entity/Product.java
@@ -1,5 +1,6 @@
package com.fc.shimpyo_be.domain.product.entity;
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
import com.fc.shimpyo_be.domain.product.util.CategoryConverter;
import com.fc.shimpyo_be.domain.room.entity.Room;
import jakarta.persistence.CascadeType;
@@ -11,6 +12,7 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
+import jakarta.persistence.OneToOne;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
@@ -18,6 +20,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
+import org.hibernate.annotations.Comment;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@@ -26,28 +29,45 @@ public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("숙소 식별자")
private Long id;
@Column(nullable = false)
+ @Comment("숙소 이름")
private String name;
- @Column(nullable = false)
- private String address;
+ @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ @Comment("숙소 위치")
+ private Address address;
@Column(nullable = false)
@Convert(converter = CategoryConverter.class)
+ @Comment("숙소 카테고리")
private Category category;
@Column(columnDefinition = "TEXT", nullable = false)
+ @Comment("숙소 설명")
private String description;
@ColumnDefault("0")
+ @Comment("숙소 평점")
private float starAvg;
@Column(columnDefinition = "TEXT", nullable = false)
+ @Comment("숙소 대표 이미지 URL")
private String thumbnail;
+ @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ @Comment("숙소 옵션 식별자")
+ private ProductOption productOption;
+ @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ @Comment("숙소 부대시설 식별자")
+ private Amenity amenity;
@OneToMany(mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List photoUrls = new ArrayList<>();
@OneToMany(mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List rooms = new ArrayList<>();
+ @OneToMany(mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ private List favorites = new ArrayList<>();
+
@Builder
- public Product(Long id, String name, String address, Category category, String description,
- float starAvg, String thumbnail) {
+ public Product(Long id, String name, Address address, Category category, String description,
+ float starAvg, String thumbnail, ProductOption productOption, Amenity amenity,
+ List photoUrls, List rooms, List favorites) {
this.id = id;
this.name = name;
this.address = address;
@@ -55,6 +75,11 @@ public Product(Long id, String name, String address, Category category, String d
this.description = description;
this.starAvg = starAvg;
this.thumbnail = thumbnail;
+ this.productOption = productOption;
+ this.amenity = amenity;
+ this.photoUrls = photoUrls;
+ this.rooms = rooms;
+ this.favorites = favorites;
}
public void updateStarAvg(float starAvg) {
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/entity/ProductImage.java b/src/main/java/com/fc/shimpyo_be/domain/product/entity/ProductImage.java
index 3370f9e4..86af3946 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/entity/ProductImage.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/entity/ProductImage.java
@@ -12,6 +12,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
@Getter
@@ -21,17 +22,20 @@ public class ProductImage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("숙소 이미지 식별자")
private Long id;
- @Column(nullable = false, columnDefinition = "TEXT")
- private String photoUrl;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(nullable = false, name = "product_id")
+ @Comment("숙소 식별자")
private Product product;
+ @Column(nullable = false, columnDefinition = "TEXT")
+ @Comment("숙소 사진 URL")
+ private String photoUrl;
@Builder
- public ProductImage(Long id, String photoUrl, Product product) {
+ public ProductImage(Long id, Product product, String photoUrl) {
this.id = id;
- this.photoUrl = photoUrl;
this.product = product;
+ this.photoUrl = photoUrl;
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/entity/ProductOption.java b/src/main/java/com/fc/shimpyo_be/domain/product/entity/ProductOption.java
new file mode 100644
index 00000000..8ec30b43
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/entity/ProductOption.java
@@ -0,0 +1,50 @@
+package com.fc.shimpyo_be.domain.product.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Entity
+public class ProductOption {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("숙소 옵션 식별자")
+ private Long id;
+ @Column(nullable = false)
+ @Comment("객실 내 취사 여부")
+ private boolean cooking;
+ @Column(nullable = false)
+ @Comment("주차 시설 여부")
+ private boolean parking;
+ @Column(nullable = false)
+ @Comment("픽업 서비스 여부")
+ private boolean pickup;
+ @Column(nullable = false)
+ @Comment("식음료장")
+ private String foodPlace;
+ @Column(nullable = true)
+ @Comment("문의 및 안내")
+ private String infoCenter;
+
+ @Builder
+ public ProductOption(Long id, boolean cooking, boolean parking, boolean pickup,
+ String foodPlace,
+ String infoCenter) {
+ this.id = id;
+ this.cooking = cooking;
+ this.parking = parking;
+ this.pickup = pickup;
+ this.foodPlace = foodPlace;
+ this.infoCenter = infoCenter;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/exception/InvalidDataException.java b/src/main/java/com/fc/shimpyo_be/domain/product/exception/InvalidDataException.java
new file mode 100644
index 00000000..312e136b
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/exception/InvalidDataException.java
@@ -0,0 +1,8 @@
+package com.fc.shimpyo_be.domain.product.exception;
+
+public class InvalidDataException extends RuntimeException {
+
+ public InvalidDataException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductCustomRepository.java b/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductCustomRepository.java
new file mode 100644
index 00000000..a344aa8f
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductCustomRepository.java
@@ -0,0 +1,12 @@
+package com.fc.shimpyo_be.domain.product.repository;
+
+import com.fc.shimpyo_be.domain.product.dto.request.SearchKeywordRequest;
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface ProductCustomRepository {
+
+ Page findAllBySearchKeywordRequest(SearchKeywordRequest searchKeywordRequest, Pageable pageable);
+
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductCustomRepositoryImpl.java b/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductCustomRepositoryImpl.java
new file mode 100644
index 00000000..210882ea
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductCustomRepositoryImpl.java
@@ -0,0 +1,109 @@
+package com.fc.shimpyo_be.domain.product.repository;
+
+
+import static com.fc.shimpyo_be.domain.product.entity.QAddress.address1;
+import static com.fc.shimpyo_be.domain.product.entity.QProduct.product;
+import static com.fc.shimpyo_be.domain.room.entity.QRoom.room;
+
+import com.fc.shimpyo_be.domain.product.dto.request.SearchKeywordRequest;
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import com.fc.shimpyo_be.global.util.QueryDslUtil;
+import com.querydsl.core.types.Order;
+import com.querydsl.core.types.OrderSpecifier;
+import com.querydsl.core.types.dsl.BooleanExpression;
+import com.querydsl.jpa.impl.JPAQuery;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.support.PageableExecutionUtils;
+import org.springframework.stereotype.Repository;
+import org.springframework.util.ObjectUtils;
+
+
+@Repository
+public class ProductCustomRepositoryImpl implements ProductCustomRepository {
+
+ private final JPAQueryFactory queryFactory;
+
+ ProductCustomRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
+ this.queryFactory = jpaQueryFactory;
+ }
+
+ public Page findAllBySearchKeywordRequest(SearchKeywordRequest searchKeywordRequest,
+ Pageable pageable) {
+
+ JPAQuery query = queryFactory
+ .selectDistinct(product)
+ .from(product)
+ .leftJoin(product.rooms, room)
+ .leftJoin(product.address, address1)
+ .where(buildSearchConditions(searchKeywordRequest))
+ .offset(pageable.getOffset())
+ .orderBy(getAllOrderSpecifiers(pageable).toArray(OrderSpecifier[]::new))
+ .limit(pageable.getPageSize());
+
+ JPAQuery countQuery = queryFactory
+ .selectDistinct(product)
+ .from(product)
+ .leftJoin(product.rooms, room)
+ .leftJoin(product.address, address1)
+ .where(buildSearchConditions(searchKeywordRequest));
+
+ List content = query.fetch();
+
+ return PageableExecutionUtils.getPage(content, pageable, () -> countQuery.fetchCount());
+ }
+
+ private BooleanExpression buildSearchConditions(SearchKeywordRequest searchKeywordRequest) {
+ List expressions = new ArrayList<>();
+
+ if (searchKeywordRequest.productName() != null) {
+ expressions.add(product.name.containsIgnoreCase(searchKeywordRequest.productName()));
+ }
+
+ if (searchKeywordRequest.address() != null) {
+ expressions.add(
+ address1.address.containsIgnoreCase(searchKeywordRequest.address()));
+ }
+
+ if (searchKeywordRequest.category() != null && !searchKeywordRequest.category().isEmpty()) {
+ expressions.add(product.category.in(searchKeywordRequest.category()));
+ }
+
+ if (searchKeywordRequest.capacity() != null) {
+ expressions.add(room.capacity.goe(searchKeywordRequest.capacity()));
+ }
+
+ return expressions.stream().reduce(BooleanExpression::and).orElse(null);
+ }
+
+
+ private List> getAllOrderSpecifiers(Pageable pageable) {
+
+ List> ORDERS = new LinkedList<>();
+
+ if (!ObjectUtils.isEmpty(pageable.getSort())) {
+ for (Sort.Order order : pageable.getSort()) {
+ Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC;
+ switch (order.getProperty()) {
+ case "starAvg" -> {
+ OrderSpecifier> orderId = QueryDslUtil.getSortedColumn(direction, product,
+ "starAvg");
+ ORDERS.add(orderId);
+ }
+ }
+ }
+ }
+
+ if (ORDERS.isEmpty()) {
+ ORDERS.add(QueryDslUtil.getSortedColumn(Order.DESC, product, "starAvg"));
+ }
+
+ return ORDERS;
+ }
+
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductRepository.java b/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductRepository.java
index f5045406..a8be6765 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductRepository.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/repository/ProductRepository.java
@@ -2,15 +2,10 @@
import com.fc.shimpyo_be.domain.product.entity.Product;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
-public interface ProductRepository extends JpaRepository,
- JpaSpecificationExecutor {
+public interface ProductRepository extends JpaRepository
+{
- Page findAll(Specification spec, Pageable pageable);
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/repository/model/ProductSpecification.java b/src/main/java/com/fc/shimpyo_be/domain/product/repository/model/ProductSpecification.java
deleted file mode 100644
index 199cc25f..00000000
--- a/src/main/java/com/fc/shimpyo_be/domain/product/repository/model/ProductSpecification.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.fc.shimpyo_be.domain.product.repository.model;
-
-import com.fc.shimpyo_be.domain.product.entity.Category;
-import com.fc.shimpyo_be.domain.product.entity.Product;
-import org.springframework.data.jpa.domain.Specification;
-
-public class ProductSpecification {
-
- public static Specification likeProductName(String productName) {
- return (root, query, CriteriaBuilder) -> CriteriaBuilder.like(root.get("name"),
- "%" + productName + "%");
- }
-
- public static Specification equalCategory(String category) {
- return (root, query, CriteriaBuilder) -> CriteriaBuilder.equal(root.get("category"),
- Category.getByName(category));
- }
-
- public static Specification likeAddress(String address) {
- return (root, query, CriteriaBuilder) -> CriteriaBuilder.like(root.get("address"),
- "%" + address + "%");
- }
-
-}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/service/OpenApiService.java b/src/main/java/com/fc/shimpyo_be/domain/product/service/OpenApiService.java
index 2e924387..63593ce3 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/service/OpenApiService.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/service/OpenApiService.java
@@ -1,19 +1,27 @@
package com.fc.shimpyo_be.domain.product.service;
+import com.fc.shimpyo_be.domain.product.entity.Address;
+import com.fc.shimpyo_be.domain.product.entity.Amenity;
import com.fc.shimpyo_be.domain.product.entity.Category;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.entity.ProductImage;
+import com.fc.shimpyo_be.domain.product.entity.ProductOption;
+import com.fc.shimpyo_be.domain.product.exception.InvalidDataException;
import com.fc.shimpyo_be.domain.product.exception.OpenApiException;
import com.fc.shimpyo_be.domain.product.repository.ProductImageRepository;
import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomImage;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
+import com.fc.shimpyo_be.domain.room.repository.RoomImageRepository;
import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
+import jakarta.annotation.PostConstruct;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalTime;
import java.util.ArrayList;
-import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
@@ -21,7 +29,9 @@
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -43,67 +53,52 @@ public class OpenApiService {
private final ProductRepository productRepository;
private final ProductImageRepository productImageRepository;
private final RoomRepository roomRepository;
+ private final RoomImageRepository roomImageRepository;
private final RestTemplate restTemplate = new RestTemplate();
private HttpEntity httpEntity;
+ @PostConstruct
+ public void init() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ httpEntity = new HttpEntity<>(headers);
+ }
+
@Transactional
public void getData(int pageSize, int pageNum) throws JSONException {
try {
- JSONObject accommodation = getAccommodation(pageSize, pageNum);
- JSONArray base = accommodation
- .getJSONObject("items")
- .getJSONArray("item");
- for (int j = 0; j < base.length(); j++) {
- JSONObject baseItem = base.getJSONObject(j);
- int contentId = baseItem.getInt("contentid");
- JSONObject infoBody = getInfo(contentId);
- if (isEmpty(infoBody)) {
- log.info("반복 정보 조회에 데이터가 없습니다. 다음 숙박 상품을 조회합니다.");
- continue;
- }
- JSONArray info = infoBody.getJSONObject("items")
- .getJSONArray("item");
- if (!hasRoom(info)) {
- log.info("숙박 상품에 방이 없습니다. 다음 숙박 상품을 조회합니다.");
- continue;
- }
- JSONObject imageBody = getImages(contentId);
- if (isEmpty(imageBody)) {
- log.info("숙박 상품에 이미지가 없습니다.다음 숙박 상품을 조회합니다.");
- continue;
- }
- JSONArray images = imageBody
- .getJSONObject("items")
- .getJSONArray("item");
- JSONObject commonBody = getCommon(contentId);
- if (isEmpty(commonBody)) {
- log.info("공통 정보 조회에 데이터가 없습니다. 다음 숙박 상품을 조회합니다.");
- continue;
- }
- JSONObject common = commonBody.getJSONObject("items")
- .getJSONArray("item")
- .getJSONObject(0);
- JSONObject introBody = getIntro(contentId);
- if (isEmpty(introBody)) {
- log.info("소개 정보 조회에 데이터가 없습니다. 다음 숙박 상품을 조회합니다.");
- continue;
- }
- JSONObject intro = introBody
- .getJSONObject("items")
- .getJSONArray("item")
- .getJSONObject(0);
- if (baseItem.getString("firstimage").isEmpty()) {
- log.info("썸네일로 사용할 이미지가 없습니다. 다음 숙박 상품을 조회합니다.");
- continue;
+ JSONArray stayArr = getItems(getAccommodation(pageSize, pageNum));
+
+ for (int j = 0; j < stayArr.length(); j++) {
+ try {
+ JSONObject stay = stayArr.getJSONObject(j);
+ int contentId = stay.getInt("contentid");
+ JSONObject info = getInfo(contentId);
+ checkInfo(info);
+ JSONArray rooms = getItems(info);
+ checkRoom(rooms);
+ JSONObject image = getImages(contentId);
+ checkImage(image);
+ JSONArray images = getItems(image);
+ JSONObject common = getCommon(contentId);
+ checkCommon(common);
+ JSONObject commonItem = getItems(common).getJSONObject(0);
+ JSONObject intro = getIntro(contentId);
+ checkIntro(intro);
+ JSONObject introItem = getItems(intro).getJSONObject(0);
+ checkIntroItem(introItem);
+ checkStay(stay);
+ Product product = saveProduct(stay, commonItem, introItem);
+ saveProductImages(product, images);
+ saveRooms(product, introItem, rooms);
+ } catch (InvalidDataException e) {
+ log.error(e.getMessage());
}
- Product product = saveProduct(baseItem, common);
- saveImages(product, images);
- saveRooms(product, intro, info);
}
} catch (Exception e) {
- log.error(e.getMessage());
+ log.error("[OpenAPI] " + e.getMessage());
throw new OpenApiException();
}
}
@@ -130,10 +125,7 @@ private JSONObject getAccommodation(int pageSize, int pageNum) throws JSONExcept
.build(true).toUri();
ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET,
httpEntity, String.class);
- log.info("숙박 정보 조회");
- return new JSONObject(response.getBody())
- .getJSONObject("response")
- .getJSONObject("body");
+ return getBody(response.getBody());
}
private JSONObject getCommon(long contentId) throws JSONException {
@@ -149,10 +141,7 @@ private JSONObject getCommon(long contentId) throws JSONException {
.build(true).toUri();
ResponseEntity commonResponse = restTemplate.exchange(uri, HttpMethod.GET,
httpEntity, String.class);
- log.info(contentId + "번 데이터 공통 정보 조회" + commonResponse.getBody());
- return new JSONObject(commonResponse.getBody())
- .getJSONObject("response")
- .getJSONObject("body");
+ return getBody(commonResponse.getBody());
}
private JSONObject getIntro(long contentId) throws JSONException {
@@ -163,10 +152,7 @@ private JSONObject getIntro(long contentId) throws JSONException {
.build(true).toUri();
ResponseEntity introResponse = restTemplate.exchange(uri, HttpMethod.GET,
httpEntity, String.class);
- log.info(contentId + "번 데이터 소개 정보 조회" + introResponse.getBody());
- return new JSONObject(introResponse.getBody())
- .getJSONObject("response")
- .getJSONObject("body");
+ return getBody(introResponse.getBody());
}
private JSONObject getInfo(long contentId) throws JSONException {
@@ -177,10 +163,7 @@ private JSONObject getInfo(long contentId) throws JSONException {
.build(true).toUri();
ResponseEntity infoResponse = restTemplate.exchange(uri, HttpMethod.GET,
httpEntity, String.class);
- log.info(contentId + "번 데이터 반복 정보 조회" + infoResponse.getBody());
- return new JSONObject(infoResponse.getBody())
- .getJSONObject("response")
- .getJSONObject("body");
+ return getBody(infoResponse.getBody());
}
private JSONObject getImages(long contentId) throws JSONException {
@@ -192,10 +175,15 @@ private JSONObject getImages(long contentId) throws JSONException {
.build(true).toUri();
ResponseEntity imageResponse = restTemplate.exchange(uri, HttpMethod.GET,
httpEntity, String.class);
- log.info(contentId + "번 데이터 이미지 정보 조회" + imageResponse.getBody());
- return new JSONObject(imageResponse.getBody())
- .getJSONObject("response")
- .getJSONObject("body");
+ return getBody(imageResponse.getBody());
+ }
+
+ private JSONArray getItems(JSONObject jsonObject) {
+ return jsonObject.getJSONObject("items").getJSONArray("item");
+ }
+
+ private JSONObject getBody(String source) {
+ return new JSONObject(source).getJSONObject("response").getJSONObject("body");
}
private boolean hasRoom(JSONArray info) throws JSONException {
@@ -208,65 +196,183 @@ private boolean hasRoom(JSONArray info) throws JSONException {
return hasRoom;
}
- private Product saveProduct(JSONObject base, JSONObject common) throws JSONException {
+ private Product saveProduct(JSONObject base, JSONObject common, JSONObject intro)
+ throws JSONException {
+ ProductOption productOption = ProductOption.builder()
+ .cooking(intro.get("chkcooking").equals("가능"))
+ .parking(intro.get("parkinglodging").equals("가능"))
+ .pickup(intro.get("pickup").equals("가능"))
+ .foodPlace(intro.getString("foodplace"))
+ .infoCenter(intro.getString("infocenterlodging"))
+ .build();
+ Amenity amenity = Amenity.builder()
+ .barbecue(intro.get("barbecue").equals("1"))
+ .beauty(intro.get("beauty").equals("1"))
+ .beverage(intro.get("beverage").equals("1"))
+ .bicycle(intro.get("bicycle").equals("1"))
+ .campfire(intro.get("campfire").equals("1"))
+ .fitness(intro.get("fitness").equals("1"))
+ .karaoke(intro.get("karaoke").equals("1"))
+ .publicBath(intro.get("publicbath").equals("1"))
+ .publicPc(intro.get("publicpc").equals("1"))
+ .sauna(intro.get("sauna").equals("1"))
+ .sports(intro.get("sports").equals("1"))
+ .seminar(intro.get("seminar").equals("1"))
+ .build();
Product product = Product.builder()
.name(base.getString("title"))
+ .address(
+ Address.builder()
+ .address(base.getString("addr1"))
+ .detailAddress(base.getString("addr2"))
+ .mapX(base.getDouble("mapx"))
+ .mapY(base.getDouble("mapy"))
+ .build()
+ )
.category(Category.getByCode(base.getString("cat3")))
- .address(base.getString("addr1") + " " + base.getString("addr2"))
.description(common.getString("overview"))
.starAvg(0)
.thumbnail(base.getString("firstimage"))
+ .photoUrls(new ArrayList<>())
+ .productOption(productOption)
+ .amenity(amenity)
.build();
return productRepository.save(product);
}
- private void saveImages(Product product, JSONArray images) throws JSONException {
- List productImages = new ArrayList<>();
+ private void saveProductImages(Product product, JSONArray images) {
for (int k = 0; k < images.length(); k++) {
- productImages.add(ProductImage.builder()
+ productImageRepository.save(ProductImage.builder()
.product(product)
.photoUrl(images.getJSONObject(k).getString("originimgurl"))
.build());
}
- productImageRepository.saveAll(productImages);
}
private void saveRooms(Product product, JSONObject intro, JSONArray info) throws JSONException {
- List rooms = new ArrayList<>();
- for (int k = 0; k < info.length(); k++) {
- JSONObject roomJson = info.getJSONObject(k);
+ for (int i = 0; i < info.length(); i++) {
+ JSONObject roomJson = info.getJSONObject(i);
+ if (roomJson.getInt("roombasecount") == 0 && roomJson.getInt("roommaxcount") == 0) {
+ continue;
+ }
if (Integer.parseInt(roomJson.getString("roomcount")) != 0) {
- for (int r = 0; r < Integer.parseInt(roomJson.getString("roomcount"));
- r++) {
- System.out.println(intro.toString());
- String[] checkIn = intro.getString("checkintime").split(":|;");
- String[] checkOut = intro.getString("checkouttime").split(":|;");
- System.out.println(roomJson);
- rooms.add(Room.builder()
+ for (int j = 0; j < Integer.parseInt(roomJson.getString("roomcount")); j++) {
+ String[] stringCheckIn = intro.getString("checkintime").split(":|;|시");
+ String[] stringCheckOut = intro.getString("checkouttime").split(":|;|시");
+ LocalTime checkIn = getTimeFromString(stringCheckIn);
+ LocalTime checkOut = getTimeFromString(stringCheckOut);
+
+ RoomPrice roomPrice = RoomPrice.builder()
+ .offWeekDaysMinFee(Integer.parseInt(
+ roomJson.getString("roomoffseasonminfee1")))
+ .offWeekendMinFee(Integer.parseInt(
+ roomJson.getString("roomoffseasonminfee2")))
+ .peakWeekDaysMinFee(Integer.parseInt(
+ roomJson.getString("roompeakseasonminfee1")))
+ .peakWeekendMinFee(Integer.parseInt(
+ roomJson.getString("roompeakseasonminfee2")))
+ .build();
+ RoomOption roomOption = RoomOption.builder()
+ .bathFacility(roomJson.get("roombathfacility").equals("Y"))
+ .bath(roomJson.get("roombath").equals("Y"))
+ .homeTheater(roomJson.get("roomhometheater").equals("Y"))
+ .airCondition(roomJson.get("roomaircondition").equals("Y"))
+ .tv(roomJson.get("roomtv").equals("Y"))
+ .pc(roomJson.get("roompc").equals("Y"))
+ .cable(roomJson.get("roomcable").equals("Y"))
+ .internet(roomJson.get("roominternet").equals("Y"))
+ .refrigerator(roomJson.get("roomrefrigerator").equals("Y"))
+ .toiletries(roomJson.get("roomtoiletries").equals("Y"))
+ .sofa(roomJson.get("roomsofa").equals("Y"))
+ .cooking(roomJson.get("roomcook").equals("Y"))
+ .diningTable(roomJson.get("roomtable").equals("Y"))
+ .hairDryer(roomJson.get("roomhairdryer").equals("Y"))
+ .build();
+ Room room = roomRepository.save(Room.builder()
.product(product)
+ .code(roomJson.getLong("roomcode"))
.name(roomJson.getString("roomtitle"))
.description(roomJson.getString("roomintro"))
.standard(
roomJson.getInt("roombasecount"))
.capacity(Math.max(roomJson.getInt("roombasecount"),
roomJson.getInt("roommaxcount")))
- .checkIn(LocalTime.of(
- Integer.parseInt(checkIn[0].substring(checkIn[0].length() - 2)),
- Integer.parseInt(checkIn[1].substring(0, 2))))
- .checkOut(LocalTime.of(
- Integer.parseInt(checkOut[0].substring(checkOut[0].length() - 2)),
- Integer.parseInt(checkOut[1].substring(0, 2))))
- .price(
- Integer.parseInt(
- roomJson.getString("roompeakseasonminfee1")))
+ .checkIn(checkIn)
+ .checkOut(checkOut)
+ .price(roomPrice)
+ .roomOption(roomOption)
+ .roomImages(new ArrayList<>())
.build());
+ for (int k = 1; k <= 5; k++) {
+ if (!roomJson.get("roomimg" + k).equals("")) {
+ roomImageRepository.save(RoomImage.builder()
+ .room(room)
+ .photoUrl(roomJson.getString("roomimg" + k))
+ .description(roomJson.getString("roomimg" + k + "alt"))
+ .build());
+ }
+ }
}
}
}
- roomRepository.saveAll(rooms);
}
private boolean isEmpty(JSONObject body) throws JSONException {
return body.getInt("totalCount") == 0;
}
+
+ private LocalTime getTimeFromString(String[] stringTime) {
+ int hour = Integer.parseInt(
+ stringTime[0].trim().substring(stringTime[0].trim().length() - 2));
+ int minute =
+ stringTime.length == 1 ? 0 : Integer.parseInt(stringTime[1].trim().substring(0, 2));
+ return LocalTime.of(hour, minute);
+ }
+
+ private void checkInfo(JSONObject info) {
+ if (isEmpty(info)) {
+ throw new InvalidDataException("반복 정보 조회에 데이터가 없습니다. 다음 숙소를 조회합니다.");
+ }
+ }
+
+ private void checkRoom(JSONArray rooms) {
+ if (!hasRoom(rooms)) {
+ throw new InvalidDataException("숙박 숙소에 방이 없습니다. 다음 숙소를 조회합니다.");
+ }
+ }
+
+ private void checkImage(JSONObject image) {
+ if (isEmpty(image)) {
+ throw new InvalidDataException("숙박 숙소에 이미지가 없습니다.다음 숙소를 조회합니다.");
+ }
+ }
+
+ private void checkCommon(JSONObject common) {
+ if (isEmpty(common)) {
+ throw new InvalidDataException("공통 정보 조회에 데이터가 없습니다. 다음 숙소를 조회합니다.");
+ }
+ }
+
+ private void checkIntro(JSONObject intro) {
+ if (isEmpty(intro)) {
+ throw new InvalidDataException("소개 정보 조회에 데이터가 없습니다. 다음 숙소를 조회합니다.");
+ }
+ }
+
+ private void checkIntroItem(JSONObject introItem) {
+ if (introItem.getString("checkintime").trim().isEmpty() || introItem.getString(
+ "checkouttime").trim().isEmpty()) {
+ throw new InvalidDataException("체크인 체크아웃 데이터가 없습니다. 다음 숙소를 조회합니다. ");
+ }
+ if (introItem.getString("checkintime").split(":|;|시").length != 2
+ || introItem.getString("checkouttime").split(":|;|시").length != 2) {
+ throw new InvalidDataException("체크인 체크아웃 데이터가 형식에 맞지 않습니다. 다음 숙소를 조회합니다.");
+ }
+ }
+
+ private void checkStay(JSONObject stay) {
+ if (stay.getString("firstimage").isEmpty()) {
+ throw new InvalidDataException("썸네일로 사용할 이미지가 없습니다. 다음 숙소를 조회합니다.");
+ }
+ }
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/service/ProductService.java b/src/main/java/com/fc/shimpyo_be/domain/product/service/ProductService.java
index aab0caad..5b28b31c 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/service/ProductService.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/service/ProductService.java
@@ -1,22 +1,27 @@
package com.fc.shimpyo_be.domain.product.service;
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
import com.fc.shimpyo_be.domain.product.dto.request.SearchKeywordRequest;
+import com.fc.shimpyo_be.domain.product.dto.response.PaginatedProductResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductDetailsResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.exception.ProductNotFoundException;
+import com.fc.shimpyo_be.domain.product.repository.ProductCustomRepositoryImpl;
import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
-import com.fc.shimpyo_be.domain.product.repository.model.ProductSpecification;
import com.fc.shimpyo_be.domain.product.util.ProductMapper;
-import com.fc.shimpyo_be.domain.room.dto.response.RoomResponse;
+import com.fc.shimpyo_be.domain.room.entity.Room;
import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
import com.fc.shimpyo_be.global.util.DateTimeUtil;
+import com.fc.shimpyo_be.global.util.SecurityUtil;
import java.time.LocalDate;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicLong;
import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
@@ -27,51 +32,57 @@ public class ProductService {
private final ProductRepository productRepository;
+ private final ProductCustomRepositoryImpl productCustomRepository;
+
private final RoomRepository roomRepository;
private final RedisTemplate restTemplate;
- public List getProducts(final SearchKeywordRequest searchKeywordRequest,
+ private final SecurityUtil securityUtil;
+
+ public PaginatedProductResponse getProducts(final SearchKeywordRequest searchKeywordRequest,
final Pageable pageable) {
- Specification spec = (root, query, criteriaBuilder) -> null;
+ Page products = Optional.of(
+ productCustomRepository.findAllBySearchKeywordRequest(searchKeywordRequest, pageable))
+ .orElseThrow();
- if (searchKeywordRequest.productName() != null) {
- spec = spec.and(
- ProductSpecification.likeProductName(searchKeywordRequest.productName()));
- }
- if (searchKeywordRequest.category() != null) {
- if (searchKeywordRequest.category().contains(",")) {
- String[] categories = searchKeywordRequest.category().split(",");
- for (int i = 0; i < categories.length; i++) {
- spec = spec.or(ProductSpecification.equalCategory(categories[i]));
- }
- } else {
- spec = spec.and(
- ProductSpecification.equalCategory(searchKeywordRequest.category()));
- }
- }
- if (searchKeywordRequest.address() != null) {
- spec = spec.and(ProductSpecification.likeAddress(searchKeywordRequest.address()));
- }
-
- return Optional.of(productRepository.findAll(spec, pageable)).orElseThrow().getContent()
- .stream().map(ProductMapper::toProductResponse).toList();
+ return PaginatedProductResponse.builder()
+ .productResponses(
+ getProductResponseSettingFavorites(products.getContent()))
+ .pageCount(products.getTotalPages())
+ .build();
}
public ProductDetailsResponse getProductDetails(final Long productId, final String startDate,
final String endDate) {
Product product = productRepository.findById(productId)
.orElseThrow(ProductNotFoundException::new);
+ HashSet favoriteProductIds = getFavoriteProductIds(List.of(product));
ProductDetailsResponse productDetailsResponse = ProductMapper.toProductDetailsResponse(
- product);
- productDetailsResponse.rooms().stream().filter(
- roomResponse -> !isAvailableForReservation(roomResponse.getRoomId(), startDate,
- endDate))
- .forEach(RoomResponse::setReserved);
+ product, favoriteProductIds.contains(product.getId()));
+
+ productDetailsResponse.rooms().forEach(
+ roomResponse -> roomResponse.setRemaining(
+ countAvailableForReservationUsingRoomCode(roomResponse.getRoomCode(), startDate,
+ endDate)));
return productDetailsResponse;
}
+ public long countAvailableForReservationUsingRoomCode(final Long roomCode,
+ final String startDate,
+ final String endDate) {
+ AtomicLong remaining = new AtomicLong();
+ List rooms = Optional.of(roomRepository.findByCode(roomCode)).orElseThrow();
+ rooms.forEach(room -> {
+ if (isAvailableForReservation(room.getId(), startDate, endDate)) {
+ remaining.getAndIncrement();
+ }
+ });
+
+ return remaining.get();
+ }
+
public boolean isAvailableForReservation(final Long roomId, final String startDate,
final String endDate) {
ValueOperations values = restTemplate.opsForValue();
@@ -82,7 +93,7 @@ public boolean isAvailableForReservation(final Long roomId, final String startDa
while (startLocalDate.isBefore(endLocalDate)) {
String accommodationDate = DateTimeUtil.toString(startLocalDate);
- if (values.get("roomId:" + String.valueOf(roomId) + ":" + accommodationDate) != null) {
+ if (values.get("roomId:" + roomId + ":" + accommodationDate) != null) {
return false;
}
startLocalDate = startLocalDate.plusDays(1);
@@ -91,5 +102,31 @@ public boolean isAvailableForReservation(final Long roomId, final String startDa
return true;
}
+ private List getProductResponseSettingFavorites(List products) {
+
+ HashSet favoriteProductIds = getFavoriteProductIds(products);
+
+ return products.stream().map(product -> ProductMapper.toProductResponse(product,
+ favoriteProductIds.contains(product.getId()))).toList();
+
+ }
+
+ private HashSet getFavoriteProductIds(List products) {
+ Long userId = securityUtil.getNullableCurrentMemberId();
+ HashSet favoriteProductId = new HashSet<>();
+ if (userId != null) {
+ for (Product product : products) {
+ for (Favorite favorite : product.getFavorites()) {
+ if (favorite.getMember().getId().equals(userId)) {
+ favoriteProductId.add(product.getId());
+ break;
+ }
+ }
+ }
+ }
+
+ return favoriteProductId;
+ }
+
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/util/ProductMapper.java b/src/main/java/com/fc/shimpyo_be/domain/product/util/ProductMapper.java
index 53dba4c4..b7b5d56e 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/util/ProductMapper.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/product/util/ProductMapper.java
@@ -1,51 +1,112 @@
package com.fc.shimpyo_be.domain.product.util;
+import com.fc.shimpyo_be.domain.product.dto.response.ProductAddressResponse;
+import com.fc.shimpyo_be.domain.product.dto.response.ProductAmenityResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductDetailsResponse;
+import com.fc.shimpyo_be.domain.product.dto.response.ProductOptionResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
+import com.fc.shimpyo_be.domain.product.entity.Address;
+import com.fc.shimpyo_be.domain.product.entity.Amenity;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.entity.ProductImage;
+import com.fc.shimpyo_be.domain.product.entity.ProductOption;
import com.fc.shimpyo_be.domain.room.entity.Room;
import com.fc.shimpyo_be.domain.room.util.RoomMapper;
+import com.fc.shimpyo_be.global.util.PricePickerByDateUtil;
import java.util.ArrayList;
import java.util.List;
public class ProductMapper {
- public static ProductResponse toProductResponse(Product product) {
+
+ public static ProductResponse toProductResponse(Product product, boolean isFavorite) {
+
return ProductResponse.builder().productId(product.getId()).productName(product.getName())
- .address(product.getAddress()).category(product.getCategory().getName())
+ .address(
+ product.getAddress().getAddress() + " " + product.getAddress().getDetailAddress())
+ .category(product.getCategory().getName())
.image(product.getThumbnail())
.starAvg(product.getStarAvg())
- .price(product.getRooms().isEmpty()
- ? 0 : Long.valueOf(
- product.getRooms().stream().map(Room::getPrice).min((o1, o2) -> o1 - o2)
- .orElseThrow()))
+ .price(getPrice(product))
.capacity(product.getRooms().isEmpty()
? 0 : Long.valueOf(
product.getRooms().stream().map(Room::getCapacity).min((o1, o2) -> o2 - o1)
.orElseThrow()))
- .favorites(false)
+ .favorites(isFavorite)
.build();
}
- public static ProductDetailsResponse toProductDetailsResponse(Product product) {
-
- List images = new ArrayList<>();
- images.add(product.getThumbnail());
- images.addAll(product.getPhotoUrls().stream().map(ProductImage::getPhotoUrl).toList());
+ public static ProductDetailsResponse toProductDetailsResponse(Product product, boolean isFavorite) {
return ProductDetailsResponse.builder()
.productId(product.getId())
.category(product.getCategory().getName())
- .address(product.getAddress())
+ .address(toProductAddressResponse(product.getAddress()))
.productName(product.getName())
.description(product.getDescription())
+ .productAmenityResponse(toProductAmenityResponse(product.getAmenity()))
.starAvg(product.getStarAvg())
- .favorites(false)
- .images(images)
- .rooms(product.getRooms().stream().map(RoomMapper::from).toList())
+ .productOptionResponse(toProductOptionResponse(product.getProductOption()))
+ .favorites(isFavorite)
+ .images(getImage(product))
+ .rooms(product.getRooms().stream().map(RoomMapper::toRoomResponse).distinct().toList())
+ .build();
+ }
+
+ private static ProductAddressResponse toProductAddressResponse(Address address) {
+ return ProductAddressResponse.builder()
+ .address(address.getAddress())
+ .detailAddress(address.getDetailAddress())
+ .mapX(address.getMapX())
+ .mapY(address.getMapY())
.build();
}
+ private static ProductAmenityResponse toProductAmenityResponse(Amenity amenity) {
+ return ProductAmenityResponse.builder()
+ .barbecue(amenity.isBarbecue())
+ .bicycle(amenity.isBicycle())
+ .beauty(amenity.isBeauty())
+ .beverage(amenity.isBeverage())
+ .sauna(amenity.isSauna())
+ .fitness(amenity.isFitness())
+ .karaoke(amenity.isKaraoke())
+ .publicBath(amenity.isPublicBath())
+ .publicPc(amenity.isPublicPc())
+ .seminar(amenity.isSeminar())
+ .sports(amenity.isSports())
+ .campfire(amenity.isCampfire())
+ .build();
+ }
+
+ private static ProductOptionResponse toProductOptionResponse(ProductOption productOption) {
+ return ProductOptionResponse.builder()
+ .pickup(productOption.isPickup())
+ .parking(productOption.isParking())
+ .cooking(productOption.isCooking())
+ .infoCenter(productOption.getInfoCenter())
+ .foodPlace(productOption.getFoodPlace())
+ .build();
+ }
+
+ private static long getPrice(Product product) {
+ List rooms = product.getRooms();
+ long price = rooms.isEmpty() ? 0 : rooms.stream().map(PricePickerByDateUtil::getPrice)
+ .min((o1, o2) -> Math.toIntExact(
+ o1 - o2)).orElseThrow();
+
+ return price == 0 ? 100000 : price;
+ }
+
+ private static List getImage(Product product) {
+ List images = new ArrayList<>();
+ images.add(product.getThumbnail());
+
+ if (product.getPhotoUrls() != null) {
+ images.addAll(product.getPhotoUrls().stream().map(ProductImage::getPhotoUrl).toList());
+ }
+
+ return images;
+ }
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/constant/ReservationValidationConstants.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/constant/ReservationValidationConstants.java
new file mode 100644
index 00000000..98d07503
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/constant/ReservationValidationConstants.java
@@ -0,0 +1,20 @@
+package com.fc.shimpyo_be.domain.reservation.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ReservationValidationConstants {
+
+ // regex
+ public static final String DATE_REGEX = "^\\d{4}-\\d{2}-\\d{2}$";
+ public static final int RESERVATION_REQ_MIN_SIZE = 1;
+ public static final int RESERVATION_REQ_MAX_SIZE = 3;
+ public static final int TOTAL_PRICE_MIN_VALUE = 0;
+
+ // validation message
+ public static final String DATE_PATTERN_MESSAGE = "올바른 날짜 형식이 아닙니다.(yyyy-MM-dd 형식으로 입력하세요.)";
+ public static final String RESERVATION_REQ_SIZE_MESSAGE = "최소 1개, 최대 3개의 객실 예약이 가능합니다.";
+ public static final String PAYMETHOD_NOTNULL_MESSAGE = "null 일 수 없습니다. 정해진 결제 수단에서 선택하세요.";
+ public static final String TOTAL_PRICE_MIN_MESSAGE = "총 결제 금액은 최소 0원 이상이어야 합니다.";
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/controller/ReservationRestController.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/controller/ReservationRestController.java
index 4af7a175..fbce2b09 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/controller/ReservationRestController.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/controller/ReservationRestController.java
@@ -3,7 +3,9 @@
import com.fc.shimpyo_be.domain.reservation.dto.request.PreoccupyRoomsRequestDto;
import com.fc.shimpyo_be.domain.reservation.dto.request.ReleaseRoomsRequestDto;
import com.fc.shimpyo_be.domain.reservation.dto.request.SaveReservationRequestDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ReservationInfoResponseDto;
import com.fc.shimpyo_be.domain.reservation.dto.response.SaveReservationResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyResultResponseDto;
import com.fc.shimpyo_be.domain.reservation.facade.PreoccupyRoomsLockFacade;
import com.fc.shimpyo_be.domain.reservation.facade.ReservationLockFacade;
import com.fc.shimpyo_be.domain.reservation.service.ReservationService;
@@ -30,12 +32,14 @@ public class ReservationRestController {
private final PreoccupyRoomsLockFacade preoccupyRoomsLockFacade;
private final ReservationLockFacade reservationLockFacade;
private final SecurityUtil securityUtil;
+ private static final int PAGE_SIZE = 10;
+ private static final int PAGE_NUM = 0;
+ private static final String PAGE_SORT_BY = "id";
@PostMapping
public ResponseEntity> saveReservation(
@Valid @RequestBody SaveReservationRequestDto request
) {
- log.info("[api][POST] /api/reservations");
return ResponseEntity
.status(HttpStatus.CREATED)
@@ -49,10 +53,9 @@ public ResponseEntity> saveReservation(
}
@GetMapping
- public ResponseEntity>> getReservationList(
- @PageableDefault(size = 10, page = 0, sort = "id", direction = Sort.Direction.DESC) Pageable pageable
+ public ResponseEntity>> getReservationList(
+ @PageableDefault(size = PAGE_SIZE, page = PAGE_NUM, sort = PAGE_SORT_BY, direction = Sort.Direction.DESC) Pageable pageable
) {
- log.info("[api][GET] /api/reservations");
return ResponseEntity
.status(HttpStatus.OK)
@@ -66,17 +69,18 @@ public ResponseEntity>> getReservationList(
}
@PostMapping("/preoccupy")
- public ResponseEntity> checkAvailableAndPreoccupy(
+ public ResponseEntity> checkAvailableAndPreoccupy(
@Valid @RequestBody PreoccupyRoomsRequestDto request
) {
- log.info("[api][POST] /api/reservations/preoccupy");
-
- preoccupyRoomsLockFacade.checkAvailableAndPreoccupy(securityUtil.getCurrentMemberId(), request);
return ResponseEntity
.status(HttpStatus.OK)
.body(
- ResponseDto.res(HttpStatus.OK, "예약 가능 유효성 검사와 객실 선점이 정상적으로 완료되었습니다.")
+ ResponseDto.res(
+ HttpStatus.OK,
+ preoccupyRoomsLockFacade.checkAvailableAndPreoccupy(securityUtil.getCurrentMemberId(), request),
+ "예약 가능 유효성 검사와 객실 선점이 정상적으로 완료되었습니다."
+ )
);
}
@@ -84,7 +88,6 @@ public ResponseEntity> checkAvailableAndPreoccupy(
public ResponseEntity> releaseRooms(
@Valid @RequestBody ReleaseRoomsRequestDto request
) {
- log.info("[api][POST] /api/reservations/release");
reservationService.releaseRooms(securityUtil.getCurrentMemberId(), request);
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/CheckAvailableRoomsResultDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/CheckAvailableRoomsResultDto.java
index 149b0e65..7f8ef272 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/CheckAvailableRoomsResultDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/CheckAvailableRoomsResultDto.java
@@ -1,11 +1,15 @@
package com.fc.shimpyo_be.domain.reservation.dto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyRoomResponseDto;
+import lombok.Builder;
+
import java.util.List;
import java.util.Map;
+@Builder
public record CheckAvailableRoomsResultDto(
boolean isAvailable,
- List unavailableIds,
- Map> recordMap
+ List roomResults,
+ Map> preoccupyMap
) {
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/ValidateReservationResultDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/ValidateReservationResultDto.java
new file mode 100644
index 00000000..87e3ebf2
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/ValidateReservationResultDto.java
@@ -0,0 +1,14 @@
+package com.fc.shimpyo_be.domain.reservation.dto;
+
+import lombok.Builder;
+
+import java.util.List;
+import java.util.Map;
+
+@Builder
+public record ValidateReservationResultDto(
+ boolean isAvailable,
+ List unavailableIds,
+ Map> confirmMap
+) {
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/PreoccupyRoomItemRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/PreoccupyRoomItemRequestDto.java
index 1c001d79..9d6aa745 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/PreoccupyRoomItemRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/PreoccupyRoomItemRequestDto.java
@@ -2,13 +2,19 @@
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
+import lombok.Builder;
+import static com.fc.shimpyo_be.domain.reservation.constant.ReservationValidationConstants.*;
+
+@Builder
public record PreoccupyRoomItemRequestDto(
@NotNull
- Long roomId,
- @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "올바른 날짜 형식이 아닙니다.(yyyy-MM-dd 형식으로 입력하세요.)")
+ Long cartId,
+ @NotNull
+ Long roomCode,
+ @Pattern(regexp = DATE_REGEX, message = DATE_PATTERN_MESSAGE)
String startDate,
- @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "올바른 날짜 형식이 아닙니다.(yyyy-MM-dd 형식으로 입력하세요.)")
+ @Pattern(regexp = DATE_REGEX, message = DATE_PATTERN_MESSAGE)
String endDate
) {
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/PreoccupyRoomsRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/PreoccupyRoomsRequestDto.java
index 251b1c6a..35080845 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/PreoccupyRoomsRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/PreoccupyRoomsRequestDto.java
@@ -2,13 +2,16 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
+import lombok.Builder;
import java.util.List;
-public record PreoccupyRoomsRequestDto(
+import static com.fc.shimpyo_be.domain.reservation.constant.ReservationValidationConstants.*;
+@Builder
+public record PreoccupyRoomsRequestDto(
@Valid
- @Size(min = 1, max = 3, message = "최소 1개, 최대 3개의 객실 예약이 가능합니다.")
+ @Size(min = RESERVATION_REQ_MIN_SIZE, max = RESERVATION_REQ_MAX_SIZE, message = RESERVATION_REQ_SIZE_MESSAGE)
List rooms
) {
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/ReleaseRoomItemRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/ReleaseRoomItemRequestDto.java
index 448aac87..46c2d3d7 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/ReleaseRoomItemRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/ReleaseRoomItemRequestDto.java
@@ -2,13 +2,17 @@
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
+import lombok.Builder;
+import static com.fc.shimpyo_be.domain.reservation.constant.ReservationValidationConstants.*;
+
+@Builder
public record ReleaseRoomItemRequestDto(
@NotNull
Long roomId,
- @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "올바른 날짜 형식이 아닙니다.(yyyy-MM-dd 형식으로 입력하세요.)")
+ @Pattern(regexp = DATE_REGEX, message = DATE_PATTERN_MESSAGE)
String startDate,
- @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "올바른 날짜 형식이 아닙니다.(yyyy-MM-dd 형식으로 입력하세요.)")
+ @Pattern(regexp = DATE_REGEX, message = DATE_PATTERN_MESSAGE)
String endDate
) {
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/ReleaseRoomsRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/ReleaseRoomsRequestDto.java
index 00a295f9..d421e828 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/ReleaseRoomsRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/ReleaseRoomsRequestDto.java
@@ -2,12 +2,16 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
+import lombok.Builder;
import java.util.List;
+import static com.fc.shimpyo_be.domain.reservation.constant.ReservationValidationConstants.*;
+
+@Builder
public record ReleaseRoomsRequestDto(
@Valid
- @Size(min = 1, max = 3, message = "최소 1개, 최대 3개의 객실 요청 정보가 필요합니다.")
+ @Size(min = RESERVATION_REQ_MIN_SIZE, max = RESERVATION_REQ_MAX_SIZE, message = RESERVATION_REQ_SIZE_MESSAGE)
List rooms
) {
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/SaveReservationRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/SaveReservationRequestDto.java
index 93fde17e..220cf3bc 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/SaveReservationRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/request/SaveReservationRequestDto.java
@@ -6,16 +6,20 @@
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
+import lombok.Builder;
import java.util.List;
+import static com.fc.shimpyo_be.domain.reservation.constant.ReservationValidationConstants.*;
+
+@Builder
public record SaveReservationRequestDto(
@Valid
- @Size(min = 1, max = 3, message = "최소 1개, 최대 3개의 객실 예약이 가능합니다.")
+ @Size(min = RESERVATION_REQ_MIN_SIZE, max = RESERVATION_REQ_MAX_SIZE, message = RESERVATION_REQ_SIZE_MESSAGE)
List reservationProducts,
- @NotNull(message = "null 일 수 없습니다. 정해진 결제 수단에서 선택하세요.")
+ @NotNull(message = PAYMETHOD_NOTNULL_MESSAGE)
PayMethod payMethod,
- @Min(value = 0, message = "총 결제 금액은 음수일 수 없습니다.")
+ @Min(value = TOTAL_PRICE_MIN_VALUE, message = TOTAL_PRICE_MIN_MESSAGE)
Integer totalPrice
) {
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ReservationInfoResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ReservationInfoResponseDto.java
index 373696f3..ecb80459 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ReservationInfoResponseDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ReservationInfoResponseDto.java
@@ -1,5 +1,8 @@
package com.fc.shimpyo_be.domain.reservation.dto.response;
+import lombok.Builder;
+
+@Builder
public record ReservationInfoResponseDto(
Long reservationId,
Long reservationProductId,
@@ -7,6 +10,7 @@ public record ReservationInfoResponseDto(
String productName,
String productImageUrl,
String productAddress,
+ String productDetailAddress,
Long roomId,
String roomName,
String startDate,
@@ -14,6 +18,7 @@ public record ReservationInfoResponseDto(
String checkIn,
String checkOut,
Integer price,
- String payMethod
+ String payMethod,
+ String createdAt
) {
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/SaveReservationResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/SaveReservationResponseDto.java
index e679dbed..531b47ba 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/SaveReservationResponseDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/SaveReservationResponseDto.java
@@ -1,25 +1,17 @@
package com.fc.shimpyo_be.domain.reservation.dto.response;
-import com.fc.shimpyo_be.domain.reservation.dto.request.SaveReservationRequestDto;
import com.fc.shimpyo_be.domain.reservation.entity.PayMethod;
import com.fc.shimpyo_be.domain.reservationproduct.dto.response.ReservationProductResponseDto;
+import lombok.Builder;
import java.util.List;
+@Builder
public record SaveReservationResponseDto(
Long reservationId,
List reservationProducts,
PayMethod payMethod,
- Integer totalPrice
+ Integer totalPrice,
+ String createdAt
) {
- public SaveReservationResponseDto(Long reservationId, SaveReservationRequestDto requestDto) {
- this(
- reservationId,
- requestDto.reservationProducts()
- .stream()
- .map(ReservationProductResponseDto::new).toList(),
- requestDto.payMethod(),
- requestDto.totalPrice()
- );
- }
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidatePreoccupyResultResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidatePreoccupyResultResponseDto.java
new file mode 100644
index 00000000..4eb8289b
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidatePreoccupyResultResponseDto.java
@@ -0,0 +1,12 @@
+package com.fc.shimpyo_be.domain.reservation.dto.response;
+
+import lombok.Builder;
+
+import java.util.List;
+
+@Builder
+public record ValidatePreoccupyResultResponseDto(
+ boolean isAvailable,
+ List roomResults
+) {
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidatePreoccupyRoomResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidatePreoccupyRoomResponseDto.java
new file mode 100644
index 00000000..92cef81c
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidatePreoccupyRoomResponseDto.java
@@ -0,0 +1,13 @@
+package com.fc.shimpyo_be.domain.reservation.dto.response;
+
+import lombok.Builder;
+
+@Builder
+public record ValidatePreoccupyRoomResponseDto(
+ Long cartId,
+ Long roomCode,
+ String startDate,
+ String endDate,
+ Long roomId
+) {
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidationResultResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidateReservationResultResponseDto.java
similarity index 63%
rename from src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidationResultResponseDto.java
rename to src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidateReservationResultResponseDto.java
index 516d2815..d84abc18 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidationResultResponseDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/dto/response/ValidateReservationResultResponseDto.java
@@ -1,8 +1,11 @@
package com.fc.shimpyo_be.domain.reservation.dto.response;
+import lombok.Builder;
+
import java.util.List;
-public record ValidationResultResponseDto(
+@Builder
+public record ValidateReservationResultResponseDto(
boolean isAvailable,
List unavailableIds
) {
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/entity/Reservation.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/entity/Reservation.java
index 4d5c4cc6..c904a365 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/entity/Reservation.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/entity/Reservation.java
@@ -8,6 +8,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
import org.springframework.util.ObjectUtils;
import java.util.ArrayList;
@@ -20,13 +21,17 @@ public class Reservation extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment(value = "예약 식별자")
private Long id;
+ @Comment(value = "예약 회원 식별자")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
+ @Comment(value = "결제 수단")
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
private PayMethod payMethod;
+ @Comment(value = "총 결제 금액")
@Column(nullable = false)
private int totalPrice;
@@ -34,7 +39,7 @@ public class Reservation extends BaseTimeEntity {
private List reservationProducts = new ArrayList<>();
@Builder
- public Reservation(
+ private Reservation(
Long id,
Member member,
PayMethod payMethod,
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/InvalidRequestException.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/InvalidRequestException.java
deleted file mode 100644
index 1991b6f6..00000000
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/InvalidRequestException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.fc.shimpyo_be.domain.reservation.exception;
-
-import com.fc.shimpyo_be.global.exception.ApplicationException;
-import com.fc.shimpyo_be.global.exception.ErrorCode;
-
-public class InvalidRequestException extends ApplicationException {
-
- public InvalidRequestException(ErrorCode errorCode) {
- super(errorCode);
- }
-}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/UnavailableRoomsException.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/PreoccupyNotAvailableException.java
similarity index 64%
rename from src/main/java/com/fc/shimpyo_be/domain/reservation/exception/UnavailableRoomsException.java
rename to src/main/java/com/fc/shimpyo_be/domain/reservation/exception/PreoccupyNotAvailableException.java
index 48d949f0..693bd869 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/UnavailableRoomsException.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/PreoccupyNotAvailableException.java
@@ -1,21 +1,21 @@
package com.fc.shimpyo_be.domain.reservation.exception;
-import com.fc.shimpyo_be.domain.reservation.dto.response.ValidationResultResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyResultResponseDto;
import com.fc.shimpyo_be.global.exception.ErrorCode;
import lombok.Getter;
@Getter
-public class UnavailableRoomsException extends RuntimeException {
+public class PreoccupyNotAvailableException extends RuntimeException {
private final ErrorCode errorCode;
- private ValidationResultResponseDto data;
+ private ValidatePreoccupyResultResponseDto data;
- public UnavailableRoomsException() {
+ public PreoccupyNotAvailableException() {
super(ErrorCode.UNAVAILABLE_ROOMS.getSimpleMessage());
this.errorCode = ErrorCode.UNAVAILABLE_ROOMS;
}
- public UnavailableRoomsException(ValidationResultResponseDto data) {
+ public PreoccupyNotAvailableException(ValidatePreoccupyResultResponseDto data) {
super(ErrorCode.UNAVAILABLE_ROOMS.getSimpleMessage());
this.errorCode = ErrorCode.UNAVAILABLE_ROOMS;
this.data = data;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/ReservationRestControllerAdvice.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/ReservationRestControllerAdvice.java
index d5ce25c7..6a65a598 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/ReservationRestControllerAdvice.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/ReservationRestControllerAdvice.java
@@ -1,6 +1,7 @@
package com.fc.shimpyo_be.domain.reservation.exception;
-import com.fc.shimpyo_be.domain.reservation.dto.response.ValidationResultResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidateReservationResultResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyResultResponseDto;
import com.fc.shimpyo_be.global.common.ResponseDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
@@ -11,9 +12,17 @@
@RestControllerAdvice
public class ReservationRestControllerAdvice {
- @ExceptionHandler(UnavailableRoomsException.class)
- public ResponseEntity> unavailableRoomsException(
- UnavailableRoomsException e) {
+ @ExceptionHandler(ReserveNotAvailableException.class)
+ public ResponseEntity> unavailableToReserveException(
+ ReserveNotAvailableException e) {
+ return ResponseEntity
+ .status(e.getErrorCode().getHttpStatus())
+ .body(ResponseDto.res(e.getErrorCode().getHttpStatus(), e.getData(), e.getMessage()));
+ }
+
+ @ExceptionHandler(PreoccupyNotAvailableException.class)
+ public ResponseEntity> unavailableToPreoccupyException(
+ PreoccupyNotAvailableException e) {
return ResponseEntity
.status(e.getErrorCode().getHttpStatus())
.body(ResponseDto.res(e.getErrorCode().getHttpStatus(), e.getData(), e.getMessage()));
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/ReserveNotAvailableException.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/ReserveNotAvailableException.java
new file mode 100644
index 00000000..44af0c37
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/exception/ReserveNotAvailableException.java
@@ -0,0 +1,23 @@
+package com.fc.shimpyo_be.domain.reservation.exception;
+
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidateReservationResultResponseDto;
+import com.fc.shimpyo_be.global.exception.ErrorCode;
+import lombok.Getter;
+
+@Getter
+public class ReserveNotAvailableException extends RuntimeException {
+
+ private final ErrorCode errorCode;
+ private ValidateReservationResultResponseDto data;
+
+ public ReserveNotAvailableException() {
+ super(ErrorCode.RESERVATION_VALIDATION_FAIL.getSimpleMessage());
+ this.errorCode = ErrorCode.RESERVATION_VALIDATION_FAIL;
+ }
+
+ public ReserveNotAvailableException(ValidateReservationResultResponseDto data) {
+ super(ErrorCode.RESERVATION_VALIDATION_FAIL.getSimpleMessage());
+ this.errorCode = ErrorCode.RESERVATION_VALIDATION_FAIL;
+ this.data = data;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/facade/PreoccupyRoomsLockFacade.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/facade/PreoccupyRoomsLockFacade.java
index a8aa2828..81892b32 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/facade/PreoccupyRoomsLockFacade.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/facade/PreoccupyRoomsLockFacade.java
@@ -2,9 +2,9 @@
import com.fc.shimpyo_be.domain.reservation.dto.CheckAvailableRoomsResultDto;
import com.fc.shimpyo_be.domain.reservation.dto.request.PreoccupyRoomsRequestDto;
-import com.fc.shimpyo_be.domain.reservation.dto.response.ValidationResultResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyResultResponseDto;
import com.fc.shimpyo_be.domain.reservation.exception.RedissonLockFailException;
-import com.fc.shimpyo_be.domain.reservation.exception.UnavailableRoomsException;
+import com.fc.shimpyo_be.domain.reservation.exception.PreoccupyNotAvailableException;
import com.fc.shimpyo_be.domain.reservation.service.PreoccupyRoomsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -23,8 +23,7 @@ public class PreoccupyRoomsLockFacade {
private final RedissonClient redissonClient;
private final PreoccupyRoomsService preoccupyRoomsService;
-
- public void checkAvailableAndPreoccupy(Long memberId, PreoccupyRoomsRequestDto request) {
+ public ValidatePreoccupyResultResponseDto checkAvailableAndPreoccupy(Long memberId, PreoccupyRoomsRequestDto request) {
RLock lock = redissonClient.getLock("check-preoccupy");
String currentWorker = Thread.currentThread().getName();
@@ -37,15 +36,22 @@ public void checkAvailableAndPreoccupy(Long memberId, PreoccupyRoomsRequestDto r
}
CheckAvailableRoomsResultDto resultDto = preoccupyRoomsService.checkAvailable(memberId, request);
+
if(!resultDto.isAvailable()) {
- log.info("[{}][check available rooms result] isAvailable = {}, unavailableIds = {}", currentWorker, false, resultDto.unavailableIds());
- throw new UnavailableRoomsException(
- new ValidationResultResponseDto(false, resultDto.unavailableIds())
+ throw new PreoccupyNotAvailableException(
+ ValidatePreoccupyResultResponseDto.builder()
+ .isAvailable(false)
+ .roomResults(resultDto.roomResults())
+ .build()
);
}
- log.info("[{}][check available rooms result] isAvailable = {}, unavailableIds = {}", currentWorker, true, resultDto.unavailableIds());
- preoccupyRoomsService.preoccupy(request, resultDto.recordMap());
+ preoccupyRoomsService.preoccupy(resultDto);
+
+ return ValidatePreoccupyResultResponseDto.builder()
+ .isAvailable(true)
+ .roomResults(resultDto.roomResults())
+ .build();
} catch (InterruptedException exception) {
log.error("exception : {}, message : {}", exception.getClass().getSimpleName(), exception.getMessage());
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/facade/ReservationLockFacade.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/facade/ReservationLockFacade.java
index a9893ce0..27615b04 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/facade/ReservationLockFacade.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/facade/ReservationLockFacade.java
@@ -1,10 +1,11 @@
package com.fc.shimpyo_be.domain.reservation.facade;
+import com.fc.shimpyo_be.domain.reservation.dto.ValidateReservationResultDto;
import com.fc.shimpyo_be.domain.reservation.dto.request.SaveReservationRequestDto;
import com.fc.shimpyo_be.domain.reservation.dto.response.SaveReservationResponseDto;
-import com.fc.shimpyo_be.domain.reservation.dto.response.ValidationResultResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidateReservationResultResponseDto;
import com.fc.shimpyo_be.domain.reservation.exception.RedissonLockFailException;
-import com.fc.shimpyo_be.domain.reservation.exception.UnavailableRoomsException;
+import com.fc.shimpyo_be.domain.reservation.exception.ReserveNotAvailableException;
import com.fc.shimpyo_be.domain.reservation.service.ReservationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -27,24 +28,25 @@ public SaveReservationResponseDto saveReservation(Long memberId, SaveReservation
String currentWorker = Thread.currentThread().getName();
try {
- boolean isLocked = lock.tryLock(2, 4, TimeUnit.SECONDS);
+ boolean isLocked = lock.tryLock(3, 5, TimeUnit.SECONDS);
if(!isLocked) {
log.error("[{}] 예약 lock 획득 실패", currentWorker);
throw new RedissonLockFailException();
}
- ValidationResultResponseDto resultDto = reservationService.validate(memberId, request.reservationProducts());
+ ValidateReservationResultDto resultDto = reservationService.validate(memberId, request.reservationProducts());
if(!resultDto.isAvailable()) {
- log.info("[{}][validate rooms result] isAvailable = {}, unavailableIds = {}", currentWorker, false, resultDto.unavailableIds());
- throw new UnavailableRoomsException(
- new ValidationResultResponseDto(false, resultDto.unavailableIds())
+ throw new ReserveNotAvailableException(
+ ValidateReservationResultResponseDto.builder()
+ .isAvailable(false)
+ .unavailableIds(resultDto.unavailableIds())
+ .build()
);
}
- log.info("[{}][validate rooms result] isAvailable = {}, unavailableIds = {}", currentWorker, true, resultDto.unavailableIds());
- return reservationService.saveReservation(memberId, request);
+ return reservationService.saveReservation(memberId, request, resultDto.confirmMap());
} catch (InterruptedException exception) {
log.error("exception : {}, message : {}", exception.getClass().getSimpleName(), exception.getMessage());
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/service/PreoccupyRoomsService.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/service/PreoccupyRoomsService.java
index ef26e6cb..9c584d94 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/service/PreoccupyRoomsService.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/service/PreoccupyRoomsService.java
@@ -1,8 +1,10 @@
package com.fc.shimpyo_be.domain.reservation.service;
import com.fc.shimpyo_be.domain.reservation.dto.CheckAvailableRoomsResultDto;
-import com.fc.shimpyo_be.domain.reservation.dto.request.PreoccupyRoomsRequestDto;
import com.fc.shimpyo_be.domain.reservation.dto.request.PreoccupyRoomItemRequestDto;
+import com.fc.shimpyo_be.domain.reservation.dto.request.PreoccupyRoomsRequestDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyRoomResponseDto;
+import com.fc.shimpyo_be.domain.room.service.RoomService;
import com.fc.shimpyo_be.global.util.DateTimeUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -11,6 +13,7 @@
import org.springframework.stereotype.Service;
import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
@@ -19,63 +22,118 @@
@Service
public class PreoccupyRoomsService {
+ private final RoomService roomService;
private final RedisTemplate redisTemplate;
- private static final String REDIS_KEY_FORMAT = "roomId:%d:%s";
+ private static final String PREOCCUPY_REDIS_KEY_FORMAT = "roomId:%d:%s";
+ private static final String CHECK_DUPLICATE_FORMAT = "%d:%s:%s";
public CheckAvailableRoomsResultDto checkAvailable(Long memberId, PreoccupyRoomsRequestDto request) {
ValueOperations opsForValue = redisTemplate.opsForValue();
boolean isAvailable = true;
- List unavailableIds = new LinkedList<>();
- Map> recordMap = new HashMap<>();
String memberIdValue = String.valueOf(memberId);
+ Map> preoccupyMap = new HashMap<>();
+ Set checkSet = new HashSet<>();
+ List roomResults = new ArrayList<>();
+
for (PreoccupyRoomItemRequestDto room : request.rooms()) {
- LocalDate targetDate = DateTimeUtil.toLocalDate(room.startDate());
+ LocalDate startDate = DateTimeUtil.toLocalDate(room.startDate());
LocalDate endDate = DateTimeUtil.toLocalDate(room.endDate());
+ Long cartId = room.cartId();
+ Long roomCode = room.roomCode();
- while(targetDate.isBefore(endDate)) {
+ List roomIds = roomService.getRoomIdsByCode(roomCode);
- String key = String.format(REDIS_KEY_FORMAT, room.roomId(), targetDate);
- Object value = opsForValue.get(key);
+ boolean roomIdCheck = false;
+ for (Long roomId : roomIds) {
- log.info("roomId: {}, targetDate: {}, value: {}", room.roomId(), targetDate, value);
- if(Objects.nonNull(value)) {
- isAvailable = false;
- unavailableIds.add(room.roomId());
- break;
+ if(checkSet.contains(String.format(CHECK_DUPLICATE_FORMAT, roomId, startDate, endDate))) {
+ continue;
+ }
+
+ boolean dateCheck = true;
+ LocalDate targetDate = startDate;
+ while(targetDate.isBefore(endDate)) {
+
+ String key = String.format(PREOCCUPY_REDIS_KEY_FORMAT, roomId, targetDate);
+ Object value = opsForValue.get(key);
+
+ if(Objects.nonNull(value)) {
+ dateCheck = false;
+ preoccupyMap.remove(roomId);
+ break;
+ }
+
+ preoccupyMap
+ .computeIfAbsent(roomId, k -> new LinkedHashMap<>())
+ .put(key, memberIdValue);
+
+ targetDate = targetDate.plusDays(1);
}
- recordMap
- .computeIfAbsent(room.roomId(), k -> new LinkedHashMap<>())
- .put(key, memberIdValue);
+ if(dateCheck) {
+ roomIdCheck = true;
+
+ roomResults.add(
+ ValidatePreoccupyRoomResponseDto.builder()
+ .cartId(cartId)
+ .roomCode(roomCode)
+ .startDate(DateTimeUtil.toString(startDate))
+ .endDate(DateTimeUtil.toString(endDate))
+ .roomId(roomId)
+ .build()
+ );
+
+ checkSet.add(String.format(CHECK_DUPLICATE_FORMAT, roomId, startDate, endDate));
- targetDate = targetDate.plusDays(1);
+ break;
+ }
+ }
+
+ if (!roomIdCheck) {
+ isAvailable = false;
+
+ roomResults.add(
+ ValidatePreoccupyRoomResponseDto.builder()
+ .cartId(cartId)
+ .roomCode(roomCode)
+ .startDate(DateTimeUtil.toString(startDate))
+ .endDate(DateTimeUtil.toString(endDate))
+ .roomId(-1L)
+ .build()
+ );
}
}
- return new CheckAvailableRoomsResultDto(isAvailable, unavailableIds, recordMap);
+ return CheckAvailableRoomsResultDto.builder()
+ .isAvailable(isAvailable)
+ .roomResults(roomResults)
+ .preoccupyMap(preoccupyMap)
+ .build();
}
- public void preoccupy(PreoccupyRoomsRequestDto request, Map> preoccupyMap) {
+ public void preoccupy(CheckAvailableRoomsResultDto resultDto) {
ValueOperations opsForValue = redisTemplate.opsForValue();
- for (PreoccupyRoomItemRequestDto room : request.rooms()) {
- Map map = preoccupyMap.get(room.roomId());
+ Map> preoccupyMap = resultDto.preoccupyMap();
+
+ for (ValidatePreoccupyRoomResponseDto roomResult : resultDto.roomResults()) {
+ Map map = preoccupyMap.get(roomResult.roomId());
opsForValue.multiSet(map);
- Date expireDate = convertLocalDateToDate(DateTimeUtil.toLocalDate(room.endDate()));
+ Date expireDate = convertLocalDateTimeToDate(LocalDateTime.now().plusMinutes(35));
for (String key : map.keySet()) {
redisTemplate.expireAt(key, expireDate);
}
}
}
- private Date convertLocalDateToDate(LocalDate localDate) {
+ private Date convertLocalDateTimeToDate(LocalDateTime dateTime) {
return Date.from(
- localDate
- .atStartOfDay(ZoneId.systemDefault())
+ dateTime
+ .atZone(ZoneId.systemDefault())
.toInstant()
);
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/service/ReservationService.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/service/ReservationService.java
index d268c269..6e2c7371 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservation/service/ReservationService.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/service/ReservationService.java
@@ -1,25 +1,22 @@
package com.fc.shimpyo_be.domain.reservation.service;
+import com.fc.shimpyo_be.domain.cart.service.CartService;
import com.fc.shimpyo_be.domain.member.entity.Member;
-import com.fc.shimpyo_be.domain.member.exception.MemberNotFoundException;
-import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
-import com.fc.shimpyo_be.domain.product.entity.Product;
-import com.fc.shimpyo_be.domain.product.exception.RoomNotFoundException;
+import com.fc.shimpyo_be.domain.member.service.MemberService;
+import com.fc.shimpyo_be.domain.reservation.dto.ValidateReservationResultDto;
import com.fc.shimpyo_be.domain.reservation.dto.request.ReleaseRoomItemRequestDto;
import com.fc.shimpyo_be.domain.reservation.dto.request.ReleaseRoomsRequestDto;
import com.fc.shimpyo_be.domain.reservation.dto.request.SaveReservationRequestDto;
import com.fc.shimpyo_be.domain.reservation.dto.response.ReservationInfoResponseDto;
import com.fc.shimpyo_be.domain.reservation.dto.response.SaveReservationResponseDto;
-import com.fc.shimpyo_be.domain.reservation.dto.response.ValidationResultResponseDto;
import com.fc.shimpyo_be.domain.reservation.entity.Reservation;
-import com.fc.shimpyo_be.domain.reservation.exception.InvalidRequestException;
import com.fc.shimpyo_be.domain.reservation.repository.ReservationRepository;
+import com.fc.shimpyo_be.domain.reservation.util.mapper.ReservationMapper;
import com.fc.shimpyo_be.domain.reservationproduct.dto.request.ReservationProductRequestDto;
import com.fc.shimpyo_be.domain.reservationproduct.entity.ReservationProduct;
import com.fc.shimpyo_be.domain.reservationproduct.repository.ReservationProductRepository;
import com.fc.shimpyo_be.domain.room.entity.Room;
-import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
-import com.fc.shimpyo_be.global.exception.ErrorCode;
+import com.fc.shimpyo_be.domain.room.service.RoomService;
import com.fc.shimpyo_be.global.util.DateTimeUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -32,11 +29,8 @@
import org.springframework.util.ObjectUtils;
import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
+import java.time.ZoneId;
+import java.util.*;
@Slf4j
@RequiredArgsConstructor
@@ -45,24 +39,30 @@ public class ReservationService {
private final ReservationRepository reservationRepository;
private final ReservationProductRepository reservationProductRepository;
- private final MemberRepository memberRepository;
- private final RoomRepository roomRepository;
+ private final MemberService memberService;
+ private final RoomService roomService;
+ private final CartService cartService;
private final RedisTemplate redisTemplate;
private static final String REDIS_ROOM_KEY_FORMAT = "roomId:%d:%s";
@Transactional
- public SaveReservationResponseDto saveReservation(Long memberId, SaveReservationRequestDto request) {
- log.info("{} ::: {}", getClass().getSimpleName(), "saveReservation");
+ public SaveReservationResponseDto saveReservation(
+ Long memberId, SaveReservationRequestDto request, Map> reservationMap
+ ) {
// 회원 엔티티 조회
- Member member = memberRepository.findById(memberId)
- .orElseThrow(MemberNotFoundException::new);
+ Member member = memberService.getMemberById(memberId);
- // 객실 엔티티 조회 후, 예약 상품 리스트 생성
+ // 객실 엔티티 조회 후, 예약 숙소 리스트 생성
List reservationProducts = new ArrayList<>();
for (ReservationProductRequestDto reservationProductDto : request.reservationProducts()) {
- Room room = roomRepository.findById(reservationProductDto.roomId())
- .orElseThrow(RoomNotFoundException::new);
+
+ Room room = roomService.getRoomById(reservationProductDto.roomId());
+
+ // 장바구니 아이템 삭제
+ if (reservationProductDto.cartId() > 0) {
+ cartService.deleteCart(member.getId(), reservationProductDto.cartId());
+ }
reservationProducts.add(
ReservationProduct.builder()
@@ -74,95 +74,82 @@ public SaveReservationResponseDto saveReservation(Long memberId, SaveReservation
.price(reservationProductDto.price())
.build()
);
+
+ confirmReservationProduct(reservationMap.get(room.getId()), reservationProductDto.endDate());
}
// 예약 저장
- Long reservationId
- = reservationRepository.save(
- Reservation.builder()
- .member(member)
- .reservationProducts(reservationProducts)
- .payMethod(request.payMethod())
- .totalPrice(request.totalPrice())
- .build()).getId();
-
- return new SaveReservationResponseDto(reservationId, request);
+ Reservation reservation = reservationRepository.save(
+ Reservation.builder()
+ .member(member)
+ .reservationProducts(reservationProducts)
+ .payMethod(request.payMethod())
+ .totalPrice(request.totalPrice())
+ .build()
+ );
+
+ return ReservationMapper.toSaveReservationResponseDto(reservation);
}
@Transactional(readOnly = true)
public Page getReservationInfoList(Long memberId, Pageable pageable) {
- log.info("{} ::: {}", getClass().getSimpleName(), "getReservationInfoList");
List reservationIds = reservationRepository.findIdsByMemberId(memberId);
- DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
- DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
return reservationProductRepository
.findAllInReservationIds(reservationIds, pageable)
- .map(
- reservationProduct -> {
- Reservation reservation = reservationProduct.getReservation();
- Room room = reservationProduct.getRoom();
- Product product = room.getProduct();
-
- return new ReservationInfoResponseDto(
- reservation.getId(),
- reservationProduct.getId(),
- product.getId(),
- product.getName(),
- product.getThumbnail(),
- product.getAddress(),
- room.getId(),
- room.getName(),
- dateFormatter.format(reservationProduct.getStartDate()),
- dateFormatter.format(reservationProduct.getEndDate()),
- timeFormatter.format(room.getCheckIn()),
- timeFormatter.format(room.getCheckOut()),
- reservationProduct.getPrice(),
- reservation.getPayMethod().name()
- );
- }
- );
+ .map(ReservationMapper::toReservationInfoResponseDto);
}
- public ValidationResultResponseDto validate(Long memberId, List reservationProducts) {
- log.info("{} ::: {}", getClass().getSimpleName(), "validate");
+ public ValidateReservationResultDto validate(Long memberId, List reservationProducts) {
ValueOperations opsForValue = redisTemplate.opsForValue();
boolean isValid = true;
List invalidRoomIds = new ArrayList<>();
+ Map> confirmMap = new HashMap<>();
+ String memberIdValue = String.valueOf(memberId);
+
for (ReservationProductRequestDto reservationProduct : reservationProducts) {
+
LocalDate targetDate = DateTimeUtil.toLocalDate(reservationProduct.startDate());
LocalDate endDate = DateTimeUtil.toLocalDate(reservationProduct.endDate());
-
+ Long roomId = reservationProduct.roomId();
List keys = new LinkedList<>();
- while(targetDate.isBefore(endDate)) {
- keys.add(String.format(REDIS_ROOM_KEY_FORMAT, reservationProduct.roomId(), targetDate));
+
+ while (targetDate.isBefore(endDate)) {
+ keys.add(String.format(REDIS_ROOM_KEY_FORMAT, roomId, targetDate));
+
targetDate = targetDate.plusDays(1);
}
+ confirmMap.put(roomId, keys);
+
List values = opsForValue.multiGet(keys);
- String memberIdValue = String.valueOf(memberId);
- if(ObjectUtils.isEmpty(values)) {
- throw new InvalidRequestException(ErrorCode.INVALID_RESERVATION_REQUEST);
+ if (ObjectUtils.isEmpty(values)) {
+ isValid = false;
+ invalidRoomIds.add(roomId);
+ continue;
}
for (Object value : values) {
- if(!memberIdValue.equals(value)) {
+ if (Objects.isNull(value) || !memberIdValue.equals(value)) {
isValid = false;
- invalidRoomIds.add(Long.valueOf((String) value));
+ invalidRoomIds.add(roomId);
break;
}
}
}
- return new ValidationResultResponseDto(isValid, invalidRoomIds);
+ return ValidateReservationResultDto.builder()
+ .isAvailable(isValid)
+ .unavailableIds(invalidRoomIds)
+ .confirmMap(confirmMap)
+ .build();
}
public void releaseRooms(Long memberId, ReleaseRoomsRequestDto request) {
- log.info("{} ::: {}", getClass().getSimpleName(), "releaseRooms");
String memberIdValue = String.valueOf(memberId);
ValueOperations opsForValue = redisTemplate.opsForValue();
@@ -185,4 +172,20 @@ public void releaseRooms(Long memberId, ReleaseRoomsRequestDto request) {
redisTemplate.delete(deleteKeys);
}
}
+
+ private void confirmReservationProduct(List reservationKeys, String endDate) {
+
+ Date expireDate = convertLocalDateToDate(DateTimeUtil.toLocalDate(endDate));
+ for (String key : reservationKeys) {
+ redisTemplate.expireAt(key, expireDate);
+ }
+ }
+
+ private Date convertLocalDateToDate(LocalDate localDate) {
+ return Date.from(
+ localDate
+ .atStartOfDay(ZoneId.systemDefault())
+ .toInstant()
+ );
+ }
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservation/util/mapper/ReservationMapper.java b/src/main/java/com/fc/shimpyo_be/domain/reservation/util/mapper/ReservationMapper.java
new file mode 100644
index 00000000..5ad17e62
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservation/util/mapper/ReservationMapper.java
@@ -0,0 +1,75 @@
+package com.fc.shimpyo_be.domain.reservation.util.mapper;
+
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ReservationInfoResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.SaveReservationResponseDto;
+import com.fc.shimpyo_be.domain.reservation.entity.Reservation;
+import com.fc.shimpyo_be.domain.reservationproduct.dto.response.ReservationProductResponseDto;
+import com.fc.shimpyo_be.domain.reservationproduct.entity.ReservationProduct;
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.global.util.DateTimeUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ReservationMapper {
+
+ public static ReservationInfoResponseDto toReservationInfoResponseDto(ReservationProduct reservationProduct) {
+ Reservation reservation = reservationProduct.getReservation();
+ Room room = reservationProduct.getRoom();
+ Product product = room.getProduct();
+
+ return ReservationInfoResponseDto.builder()
+ .reservationId(reservation.getId())
+ .reservationProductId(reservationProduct.getId())
+ .productId(product.getId())
+ .productName(product.getName())
+ .productImageUrl(product.getThumbnail())
+ .productAddress(product.getAddress().getAddress())
+ .productDetailAddress(product.getAddress().getDetailAddress())
+ .roomId(room.getId())
+ .roomName(room.getName())
+ .startDate(DateTimeUtil.toString(reservationProduct.getStartDate()))
+ .endDate(DateTimeUtil.toString(reservationProduct.getEndDate()))
+ .checkIn(DateTimeUtil.toString(room.getCheckIn()))
+ .checkOut(DateTimeUtil.toString(room.getCheckOut()))
+ .price(reservationProduct.getPrice())
+ .payMethod(reservation.getPayMethod().name())
+ .createdAt(DateTimeUtil.toString(reservation.getCreatedAt()))
+ .build();
+ }
+
+ public static SaveReservationResponseDto toSaveReservationResponseDto(Reservation reservation) {
+ List reservationProductDtos = new ArrayList<>();
+ for (ReservationProduct reservationProduct : reservation.getReservationProducts()) {
+
+ Room room = reservationProduct.getRoom();
+ Product product = room.getProduct();
+
+ reservationProductDtos.add(
+ ReservationProductResponseDto.builder()
+ .productName(product.getName())
+ .roomId(room.getId())
+ .roomName(room.getName())
+ .standard(room.getStandard())
+ .capacity(room.getCapacity())
+ .startDate(DateTimeUtil.toString(reservationProduct.getStartDate()))
+ .endDate(DateTimeUtil.toString(reservationProduct.getEndDate()))
+ .checkIn(DateTimeUtil.toString(room.getCheckIn()))
+ .checkOut(DateTimeUtil.toString(room.getCheckOut()))
+ .visitorName(reservationProduct.getVisitorName())
+ .visitorPhone(reservationProduct.getVisitorPhone())
+ .price(reservationProduct.getPrice())
+ .build()
+ );
+ }
+
+ return SaveReservationResponseDto.builder()
+ .reservationId(reservation.getId())
+ .reservationProducts(reservationProductDtos)
+ .payMethod(reservation.getPayMethod())
+ .totalPrice(reservation.getTotalPrice())
+ .createdAt(DateTimeUtil.toString(reservation.getCreatedAt()))
+ .build();
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/constant/ReservationProductValidationConstants.java b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/constant/ReservationProductValidationConstants.java
new file mode 100644
index 00000000..b7b29653
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/constant/ReservationProductValidationConstants.java
@@ -0,0 +1,18 @@
+package com.fc.shimpyo_be.domain.reservationproduct.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ReservationProductValidationConstants {
+
+ // validation constraint value
+ public static final String DATE_REGEX = "^\\d{4}-\\d{2}-\\d{2}$";
+ public static final String PHONE_NUMBER_REGEX = "^01(?:0|1|[6-9])-\\d{4}-\\d{4}$";
+ public static final int PRICE_MIN_VALUE = 0;
+
+ // validation message
+ public static final String DATE_PATTERN_MESSAGE = "올바른 날짜 형식이 아닙니다.(yyyy-MM-dd 형식으로 입력하세요.)";
+ public static final String PHONE_NUMBER_PATTERN_MESSAGE = "올바른 휴대전화 번호를 입력하세요.('-' 포함)";
+ public static final String PRICE_MIN_MESSAGE = "객실 이용 금액은 0원 이상부터 가능합니다.";
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/controller/ReservationProductRestController.java b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/controller/ReservationProductRestController.java
index eca49c73..51bd865d 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/controller/ReservationProductRestController.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/controller/ReservationProductRestController.java
@@ -23,12 +23,11 @@ public class ReservationProductRestController {
@DeleteMapping("/{id}")
public ResponseEntity> cancel(@PathVariable Long id) {
- log.info("[api][DELETE] /api/reservation-products");
reservationProductService.cancel(id, securityUtil.getCurrentMemberId());
return ResponseEntity
.status(HttpStatus.OK)
- .body(ResponseDto.res(HttpStatus.OK, "예약 상품이 정상적으로 취소 처리되었습니다."));
+ .body(ResponseDto.res(HttpStatus.OK, "예약 숙소이 정상적으로 취소 처리되었습니다."));
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/dto/request/ReservationProductRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/dto/request/ReservationProductRequestDto.java
index dce3fe8a..cf73318a 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/dto/request/ReservationProductRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/dto/request/ReservationProductRequestDto.java
@@ -4,29 +4,26 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
+import lombok.Builder;
+import static com.fc.shimpyo_be.domain.reservationproduct.constant.ReservationProductValidationConstants.*;
+
+@Builder
public record ReservationProductRequestDto(
@NotNull
- Long roomId,
- @NotBlank
- String productName,
- @NotBlank
- String roomName,
- @NotNull
- Integer standard,
+ Long cartId,
@NotNull
- Integer max,
- @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "올바른 날짜 형식이 아닙니다.(yyyy-MM-dd 형식으로 입력하세요.)")
+ Long roomId,
+ @Pattern(regexp = DATE_REGEX, message = DATE_PATTERN_MESSAGE)
String startDate,
- @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "올바른 날짜 형식이 아닙니다.(yyyy-MM-dd 형식으로 입력하세요.)")
+ @Pattern(regexp = DATE_REGEX, message = DATE_PATTERN_MESSAGE)
String endDate,
- @Pattern(regexp = "^\\d{2}:\\d{2}$", message = "올바른 시간 형식이 아닙니다.(HH:mm 형식으로 입력하세요.)")
- String checkIn,
- @Pattern(regexp = "^\\d{2}:\\d{2}$", message = "올바른 시간 형식이 아닙니다.(HH:mm 형식으로 입력하세요.)")
- String checkOut,
+ @NotBlank
String visitorName,
+ @NotBlank
+ @Pattern(regexp = PHONE_NUMBER_REGEX, message = PHONE_NUMBER_PATTERN_MESSAGE)
String visitorPhone,
- @Min(value = 0, message = "객실 이용 금액은 음수일 수 없습니다.")
+ @Min(value = PRICE_MIN_VALUE, message = PRICE_MIN_MESSAGE)
Integer price
) {
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/dto/response/ReservationProductResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/dto/response/ReservationProductResponseDto.java
index 7452a5ff..41861df5 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/dto/response/ReservationProductResponseDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/dto/response/ReservationProductResponseDto.java
@@ -1,13 +1,14 @@
package com.fc.shimpyo_be.domain.reservationproduct.dto.response;
-import com.fc.shimpyo_be.domain.reservationproduct.dto.request.ReservationProductRequestDto;
+import lombok.Builder;
+@Builder
public record ReservationProductResponseDto(
- Long roomId,
String productName,
+ Long roomId,
String roomName,
Integer standard,
- Integer max,
+ Integer capacity,
String startDate,
String endDate,
String checkIn,
@@ -16,20 +17,4 @@ public record ReservationProductResponseDto(
String visitorPhone,
Integer price
) {
- public ReservationProductResponseDto(ReservationProductRequestDto requestDto) {
- this(
- requestDto.roomId(),
- requestDto.productName(),
- requestDto.roomName(),
- requestDto.standard(),
- requestDto.max(),
- requestDto.startDate(),
- requestDto.endDate(),
- requestDto.checkIn(),
- requestDto.checkOut(),
- requestDto.visitorName(),
- requestDto.visitorPhone(),
- requestDto.price()
- );
- }
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/entity/ReservationProduct.java b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/entity/ReservationProduct.java
index bcad45e7..8f399b97 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/entity/ReservationProduct.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/entity/ReservationProduct.java
@@ -8,6 +8,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -19,24 +20,32 @@ public class ReservationProduct extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment(value = "예약 숙소 식별자")
private Long id;
+ @Comment(value = "예약 주문 식별자")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reservation_id")
private Reservation reservation;
+ @Comment(value = "예약 객실 식별자")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "room_id")
private Room room;
+ @Comment(value = "총 이용 금액")
@Column(nullable = false)
private int price;
+ @Comment(value = "숙박 시작일")
@Column(nullable = false)
private LocalDate startDate;
+ @Comment(value = "숙박 마지막일")
@Column(nullable = false)
private LocalDate endDate;
+ @Comment(value = "방문자명")
private String visitorName;
+ @Comment(value = "방문자 전화번호")
private String visitorPhone;
@Builder
- public ReservationProduct(
+ private ReservationProduct(
Long id,
Reservation reservation,
Room room,
diff --git a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/service/ReservationProductService.java b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/service/ReservationProductService.java
index e5127b21..df1daefb 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/service/ReservationProductService.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/reservationproduct/service/ReservationProductService.java
@@ -19,7 +19,6 @@ public class ReservationProductService {
@Transactional
public void cancel(Long id, Long memberId) {
- log.info("{} ::: {}", getClass().getSimpleName(), "cancel");
ReservationProduct reservationProduct = reservationProductRepository.findByIdWithReservation(id)
.orElseThrow(ReservationProductNotFoundException::new);
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/constant/RoomValidationConstants.java b/src/main/java/com/fc/shimpyo_be/domain/room/constant/RoomValidationConstants.java
new file mode 100644
index 00000000..2807d7b9
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/constant/RoomValidationConstants.java
@@ -0,0 +1,17 @@
+package com.fc.shimpyo_be.domain.room.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class RoomValidationConstants {
+
+ // validation constraint value
+ public static final int ROOM_REQ_MIN_SIZE = 1;
+ public static final int ROOM_REQ_MAX_SIZE = 3;
+ public static final long ROOMID_MIN_VALUE = 1;
+
+ // validation message
+ public static final String ROOM_REQ_SIZE_MESSAGE = "최소 1개, 최대 3개의 객실 식별자 정보가 필요합니다.";
+ public static final String ROOMID_MIN_MESSAGE = "객실 식별자는 최소 1 이상이어야 합니다.";
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/controller/RoomRestController.java b/src/main/java/com/fc/shimpyo_be/domain/room/controller/RoomRestController.java
new file mode 100644
index 00000000..9e26d2a8
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/controller/RoomRestController.java
@@ -0,0 +1,49 @@
+package com.fc.shimpyo_be.domain.room.controller;
+
+import com.fc.shimpyo_be.domain.room.dto.response.RoomListWithProductInfoResponseDto;
+import com.fc.shimpyo_be.domain.room.service.RoomService;
+import com.fc.shimpyo_be.global.common.ResponseDto;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.Size;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static com.fc.shimpyo_be.domain.room.constant.RoomValidationConstants.*;
+
+@Slf4j
+@RequiredArgsConstructor
+@Validated
+@RequestMapping("/api/rooms")
+@RestController
+public class RoomRestController {
+
+ private final RoomService roomService;
+
+ @GetMapping
+ public ResponseEntity> getRoomsWithProductInfo(
+ @RequestParam @Size(min = ROOM_REQ_MIN_SIZE, max = ROOM_REQ_MAX_SIZE, message = ROOM_REQ_SIZE_MESSAGE)
+ List<@Min(value = ROOMID_MIN_VALUE, message = ROOMID_MIN_MESSAGE) Long> roomIds
+ ) {
+
+ return ResponseEntity
+ .status(HttpStatus.OK)
+ .body(
+ ResponseDto.res(
+ HttpStatus.OK,
+ new RoomListWithProductInfoResponseDto(
+ roomService.getRoomsWithProductInfo(roomIds)
+ ),
+ "숙소 정보를 포함한 객실 정보 리스트가 정상적으로 조회되었습니다."
+ )
+ );
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomListWithProductInfoResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomListWithProductInfoResponseDto.java
new file mode 100644
index 00000000..467e9774
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomListWithProductInfoResponseDto.java
@@ -0,0 +1,11 @@
+package com.fc.shimpyo_be.domain.room.dto.response;
+
+import lombok.Builder;
+
+import java.util.List;
+
+@Builder
+public record RoomListWithProductInfoResponseDto(
+ List rooms
+) {
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomOptionResponse.java b/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomOptionResponse.java
new file mode 100644
index 00000000..90e94feb
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomOptionResponse.java
@@ -0,0 +1,53 @@
+package com.fc.shimpyo_be.domain.room.dto.response;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+public record RoomOptionResponse(
+
+ boolean bathFacility,
+ boolean bath,
+ boolean homeTheater,
+ boolean airCondition,
+ boolean tv,
+ boolean pc,
+ boolean cable,
+ boolean internet,
+ boolean refrigerator,
+ boolean toiletries,
+ boolean sofa,
+ boolean cooking,
+ boolean table,
+ boolean hairDryer
+
+) {
+
+ @Builder
+ public RoomOptionResponse(boolean bathFacility, boolean bath, boolean homeTheater,
+ boolean airCondition, boolean tv, boolean pc, boolean cable, boolean internet,
+ boolean refrigerator, boolean toiletries, boolean sofa, boolean cooking, boolean table,
+ boolean hairDryer) {
+ this.bathFacility = bathFacility;
+ this.bath = bath;
+ this.homeTheater = homeTheater;
+ this.airCondition = airCondition;
+ this.tv = tv;
+ this.pc = pc;
+ this.cable = cable;
+ this.internet = internet;
+ this.refrigerator = refrigerator;
+ this.toiletries = toiletries;
+ this.sofa = sofa;
+ this.cooking = cooking;
+ this.table = table;
+ this.hairDryer = hairDryer;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomResponse.java b/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomResponse.java
index ab027eea..7a0a51f1 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomResponse.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomResponse.java
@@ -1,12 +1,15 @@
package com.fc.shimpyo_be.domain.room.dto.response;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
import lombok.Builder;
import lombok.Getter;
@Getter
public class RoomResponse {
- private final Long roomId;
+ private final Long roomCode;
private final String roomName;
private final Long price;
private final String description;
@@ -14,24 +17,53 @@ public class RoomResponse {
private final Long capacity;
private final String checkIn;
private final String checkOut;
- private Boolean reserved;
+ private final RoomOptionResponse roomOptionResponse;
+ private final List roomImages;
+ private Long remaining;
@Builder
- public RoomResponse(Long roomId, String roomName, Long price, String description, Long standard,
- Long capacity, String checkIn, String checkOut, Boolean reserved) {
- this.roomId = roomId;
+ private RoomResponse(Long roomCode, String roomName, Long price, String description,
+ Long standard,
+ Long capacity, String checkIn, String checkOut, Long remaining,
+ RoomOptionResponse roomOptionResponse,
+ List roomImages) {
+ this.roomCode = roomCode;
this.roomName = roomName;
this.price = price;
- this.reserved = reserved;
+ this.remaining = remaining;
this.description = description;
this.standard = standard;
this.capacity = capacity;
this.checkIn = checkIn;
this.checkOut = checkOut;
+ this.roomOptionResponse = roomOptionResponse;
+ if (roomImages == null) {
+ this.roomImages = new ArrayList<>();
+ } else {
+ this.roomImages = roomImages;
+ }
}
- public void setReserved() {
- reserved = true;
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ RoomResponse otherRoomResponse = (RoomResponse) obj;
+ return Objects.equals(roomCode, otherRoomResponse.roomCode);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(roomCode);
+ }
+
+ public void setRemaining(long remaining) {
+ this.remaining = remaining;
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomWithProductResponseDto.java b/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomWithProductResponseDto.java
new file mode 100644
index 00000000..bcf2ad7d
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/dto/response/RoomWithProductResponseDto.java
@@ -0,0 +1,20 @@
+package com.fc.shimpyo_be.domain.room.dto.response;
+
+import lombok.Builder;
+
+@Builder
+public record RoomWithProductResponseDto(
+ Long productId,
+ String productName,
+ String productThumbnail,
+ String productAddress,
+ String productDetailAddress,
+ Long roomId,
+ String roomName,
+ Integer standard,
+ Integer capacity,
+ String checkIn,
+ String checkOut,
+ Long price
+) {
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/entity/Room.java b/src/main/java/com/fc/shimpyo_be/domain/room/entity/Room.java
index 0f79a02c..6056e7a2 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/room/entity/Room.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/entity/Room.java
@@ -1,6 +1,7 @@
package com.fc.shimpyo_be.domain.room.entity;
import com.fc.shimpyo_be.domain.product.entity.Product;
+import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
@@ -9,11 +10,18 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.OneToOne;
+
import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@@ -22,39 +30,61 @@ public class Room {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("객실 식별자")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
+ @Comment("숙소 식별자")
private Product product;
- @Column(length = 30)
- private String name;
@Column(nullable = false)
+ @Comment("객실 코드")
+ private long code;
+ @Column(nullable = false)
+ @Comment("객실 이름")
+ private String name;
+ @Column(columnDefinition = "TEXT", nullable = false)
+ @Comment("객실 설명")
private String description;
@Column(columnDefinition = "TINYINT")
+ @Comment("객실 기준인원")
private int standard;
@Column(columnDefinition = "TINYINT")
+ @Comment("객실 최대인원")
private int capacity;
@Column(columnDefinition = "TIME")
+ @Comment("객실 체크인 시간")
private LocalTime checkIn;
@Column(columnDefinition = "TIME")
+ @Comment("객실 체크아웃 시간")
private LocalTime checkOut;
- @Column(nullable = false)
- private int price;
+ @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ @Comment("객실 가격")
+ private RoomPrice price;
+ @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ @Comment("객실 옵션 식별자")
+ private RoomOption roomOption;
+ @OneToMany(mappedBy = "room", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ @Comment("객실 이미지 식별자")
+ private List roomImages = new ArrayList<>();
@Builder
- public Room(
+ private Room(
Long id,
Product product,
+ long code,
String name,
String description,
int standard,
int capacity,
- int price,
LocalTime checkIn,
- LocalTime checkOut
+ LocalTime checkOut,
+ RoomPrice price,
+ RoomOption roomOption,
+ List roomImages
) {
this.id = id;
this.product = product;
+ this.code = code;
this.name = name;
this.description = description;
this.standard = standard;
@@ -62,5 +92,7 @@ public Room(
this.checkIn = checkIn;
this.checkOut = checkOut;
this.price = price;
+ this.roomOption = roomOption;
+ this.roomImages = roomImages;
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/entity/RoomImage.java b/src/main/java/com/fc/shimpyo_be/domain/room/entity/RoomImage.java
new file mode 100644
index 00000000..509317fa
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/entity/RoomImage.java
@@ -0,0 +1,45 @@
+package com.fc.shimpyo_be.domain.room.entity;
+
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Entity
+public class RoomImage {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("객실 이미지 식별자")
+ private Long id;
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(nullable = false, name = "room_id")
+ @Comment("객실 식별자")
+ private Room room;
+ @Column(nullable = false)
+ @Comment("객실 이미지 URL")
+ private String photoUrl;
+ @Column(nullable = false)
+ @Comment("객실 이미지 설명")
+ private String description;
+
+ @Builder
+ private RoomImage(Long id, Room room, String photoUrl, String description) {
+ this.id = id;
+ this.room = room;
+ this.photoUrl = photoUrl;
+ this.description = description;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/entity/RoomOption.java b/src/main/java/com/fc/shimpyo_be/domain/room/entity/RoomOption.java
new file mode 100644
index 00000000..976d718a
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/entity/RoomOption.java
@@ -0,0 +1,87 @@
+package com.fc.shimpyo_be.domain.room.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Entity
+public class RoomOption {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("객실 옵션 식별자")
+ private Long id;
+ @Column(nullable = false)
+ @Comment("목욕 시설 여부")
+ private boolean bathFacility;
+ @Column(nullable = false)
+ @Comment("욕조 여부")
+ private boolean bath;
+ @Column(nullable = false)
+ @Comment("홈시어터 여부")
+ private boolean homeTheater;
+ @Column(nullable = false)
+ @Comment("에어컨 여부")
+ private boolean airCondition;
+ @Column(nullable = false)
+ @Comment("TV 여부")
+ private boolean tv;
+ @Column(nullable = false)
+ @Comment("PC 여부")
+ private boolean pc;
+ @Column(nullable = false)
+ @Comment("케이블 설치 여부")
+ private boolean cable;
+ @Column(nullable = false)
+ @Comment("인터넷 여부")
+ private boolean internet;
+ @Column(nullable = false)
+ @Comment("냉장고 여부")
+ private boolean refrigerator;
+ @Column(nullable = false)
+ @Comment("세면도구 여부")
+ private boolean toiletries;
+ @Column(nullable = false)
+ @Comment("소파 여부")
+ private boolean sofa;
+ @Column(nullable = false)
+ @Comment("취사용품 여부")
+ private boolean cooking;
+ @Column(nullable = false)
+ @Comment("테이블 여부")
+ private boolean diningTable;
+ @Column(nullable = false)
+ @Comment("드라이기 여부")
+ private boolean hairDryer;
+
+ @Builder
+ private RoomOption(Long id, boolean bathFacility, boolean bath, boolean homeTheater,
+ boolean airCondition, boolean tv, boolean pc, boolean cable, boolean internet,
+ boolean refrigerator, boolean toiletries, boolean sofa, boolean cooking, boolean diningTable,
+ boolean hairDryer) {
+ this.id = id;
+ this.bathFacility = bathFacility;
+ this.bath = bath;
+ this.homeTheater = homeTheater;
+ this.airCondition = airCondition;
+ this.tv = tv;
+ this.pc = pc;
+ this.cable = cable;
+ this.internet = internet;
+ this.refrigerator = refrigerator;
+ this.toiletries = toiletries;
+ this.sofa = sofa;
+ this.cooking = cooking;
+ this.diningTable = diningTable;
+ this.hairDryer = hairDryer;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/entity/RoomPrice.java b/src/main/java/com/fc/shimpyo_be/domain/room/entity/RoomPrice.java
new file mode 100644
index 00000000..461dcb2e
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/entity/RoomPrice.java
@@ -0,0 +1,45 @@
+package com.fc.shimpyo_be.domain.room.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
+
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Entity
+public class RoomPrice {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment("객실 가격 식별자")
+ private Long id;
+ @Column(nullable = false)
+ @Comment("비수기 주중 최소 가격")
+ private int offWeekDaysMinFee;
+ @Column(nullable = false)
+ @Comment("비수기 주말 최소 가격")
+ private int offWeekendMinFee;
+ @Column(nullable = false)
+ @Comment("성수기 주중 최소 가격")
+ private int peakWeekDaysMinFee;
+ @Column(nullable = false)
+ @Comment("성수기 주말 최소 가격")
+ private int peakWeekendMinFee;
+
+ @Builder
+ private RoomPrice(Long id, int offWeekDaysMinFee, int offWeekendMinFee, int peakWeekDaysMinFee,
+ int peakWeekendMinFee) {
+ this.id = id;
+ this.offWeekDaysMinFee = offWeekDaysMinFee;
+ this.offWeekendMinFee = offWeekendMinFee;
+ this.peakWeekDaysMinFee = peakWeekDaysMinFee;
+ this.peakWeekendMinFee = peakWeekendMinFee;
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/product/exception/RoomNotFoundException.java b/src/main/java/com/fc/shimpyo_be/domain/room/exception/RoomNotFoundException.java
similarity index 83%
rename from src/main/java/com/fc/shimpyo_be/domain/product/exception/RoomNotFoundException.java
rename to src/main/java/com/fc/shimpyo_be/domain/room/exception/RoomNotFoundException.java
index 6afb1ae5..02d5d673 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/product/exception/RoomNotFoundException.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/exception/RoomNotFoundException.java
@@ -1,4 +1,4 @@
-package com.fc.shimpyo_be.domain.product.exception;
+package com.fc.shimpyo_be.domain.room.exception;
import com.fc.shimpyo_be.global.exception.ApplicationException;
import com.fc.shimpyo_be.global.exception.ErrorCode;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomImageRepository.java b/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomImageRepository.java
new file mode 100644
index 00000000..be9b263d
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomImageRepository.java
@@ -0,0 +1,8 @@
+package com.fc.shimpyo_be.domain.room.repository;
+
+import com.fc.shimpyo_be.domain.room.entity.RoomImage;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface RoomImageRepository extends JpaRepository {
+
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepository.java b/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepository.java
index b0478fdd..d1c283d3 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepository.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepository.java
@@ -2,7 +2,16 @@
import com.fc.shimpyo_be.domain.room.entity.Room;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
-public interface RoomRepository extends JpaRepository {
+import java.util.List;
+public interface RoomRepository
+ extends JpaRepository, RoomRepositoryCustom {
+
+ List findByCode(long code);
+
+ @Query("select r.id from Room r where r.code = :code")
+ List findIdsByCode(@Param("code") Long code);
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepositoryCustom.java b/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepositoryCustom.java
new file mode 100644
index 00000000..0a68eacd
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepositoryCustom.java
@@ -0,0 +1,10 @@
+package com.fc.shimpyo_be.domain.room.repository;
+
+import com.fc.shimpyo_be.domain.room.entity.Room;
+
+import java.util.List;
+
+public interface RoomRepositoryCustom {
+
+ List findAllInIdsWithProductAndPrice(List roomIds);
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepositoryImpl.java b/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepositoryImpl.java
new file mode 100644
index 00000000..9c988291
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/repository/RoomRepositoryImpl.java
@@ -0,0 +1,26 @@
+package com.fc.shimpyo_be.domain.room.repository;
+
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+import static com.fc.shimpyo_be.domain.product.entity.QProduct.product;
+import static com.fc.shimpyo_be.domain.room.entity.QRoom.room;
+import static com.fc.shimpyo_be.domain.room.entity.QRoomPrice.roomPrice;
+
+@RequiredArgsConstructor
+public class RoomRepositoryImpl implements RoomRepositoryCustom {
+
+ private final JPAQueryFactory jpaQueryFactory;
+
+ @Override
+ public List findAllInIdsWithProductAndPrice(List roomIds) {
+ return jpaQueryFactory.selectFrom(room)
+ .join(room.product, product).fetchJoin()
+ .join(room.price, roomPrice).fetchJoin()
+ .where(room.id.in(roomIds))
+ .fetch();
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/service/RoomService.java b/src/main/java/com/fc/shimpyo_be/domain/room/service/RoomService.java
new file mode 100644
index 00000000..87cb8142
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/service/RoomService.java
@@ -0,0 +1,42 @@
+package com.fc.shimpyo_be.domain.room.service;
+
+import com.fc.shimpyo_be.domain.room.exception.RoomNotFoundException;
+import com.fc.shimpyo_be.domain.room.dto.response.RoomWithProductResponseDto;
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
+import com.fc.shimpyo_be.domain.room.util.RoomMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class RoomService {
+
+ private final RoomRepository roomRepository;
+
+ @Transactional(readOnly = true)
+ public List getRoomsWithProductInfo(List roomIds) {
+
+ return roomRepository.findAllInIdsWithProductAndPrice(roomIds)
+ .stream()
+ .map(RoomMapper::toRoomWithProductResponse)
+ .toList();
+ }
+
+ @Transactional(readOnly = true)
+ public List getRoomIdsByCode(Long roomCode) {
+
+ return roomRepository.findIdsByCode(roomCode);
+ }
+
+ @Transactional(readOnly = true)
+ public Room getRoomById(Long id) {
+ return roomRepository.findById(id)
+ .orElseThrow(RoomNotFoundException::new);
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/room/util/RoomMapper.java b/src/main/java/com/fc/shimpyo_be/domain/room/util/RoomMapper.java
index b0c6e36c..19d3fdf3 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/room/util/RoomMapper.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/room/util/RoomMapper.java
@@ -1,21 +1,74 @@
package com.fc.shimpyo_be.domain.room.util;
+import com.fc.shimpyo_be.domain.product.entity.Address;
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import com.fc.shimpyo_be.domain.room.dto.response.RoomOptionResponse;
import com.fc.shimpyo_be.domain.room.dto.response.RoomResponse;
+import com.fc.shimpyo_be.domain.room.dto.response.RoomWithProductResponseDto;
import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomImage;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.global.util.DateTimeUtil;
+import com.fc.shimpyo_be.global.util.PricePickerByDateUtil;
public class RoomMapper {
- public static RoomResponse from(Room room) {
+ public static RoomResponse toRoomResponse(Room room) {
+
+ long price = PricePickerByDateUtil.getPrice(room);
+ price = price == 0 ? 100000 : price;
+
return RoomResponse.builder()
- .roomId(room.getId())
+ .roomCode(room.getCode())
.roomName(room.getName())
- .price((long) (room.getPrice()))
+ .price(price)
.standard((long) (room.getStandard()))
.capacity((long) room.getCapacity())
.description(room.getDescription())
- .reserved(false)
.checkIn(room.getCheckIn().toString())
.checkOut(room.getCheckOut().toString())
+ .roomOptionResponse(toRoomOptionResponse(room.getRoomOption()))
+ .roomImages(room.getRoomImages().stream().map(RoomImage::getPhotoUrl).toList())
+ .remaining(0L)
+ .build();
+ }
+
+ private static RoomOptionResponse toRoomOptionResponse(RoomOption roomOption) {
+ return RoomOptionResponse.builder()
+ .airCondition(roomOption.isAirCondition())
+ .pc(roomOption.isPc())
+ .bath(roomOption.isBath())
+ .bathFacility(roomOption.isBathFacility())
+ .cooking(roomOption.isCooking())
+ .tv(roomOption.isTv())
+ .cable(roomOption.isCable())
+ .hairDryer(roomOption.isHairDryer())
+ .sofa(roomOption.isSofa())
+ .table(roomOption.isDiningTable())
+ .toiletries(roomOption.isToiletries())
+ .homeTheater(roomOption.isHomeTheater())
+ .internet(roomOption.isInternet())
+ .refrigerator(roomOption.isRefrigerator())
+ .build();
+ }
+
+ public static RoomWithProductResponseDto toRoomWithProductResponse(Room room) {
+ Product product = room.getProduct();
+ Address productAddress = product.getAddress();
+
+ return RoomWithProductResponseDto.builder()
+ .productId(product.getId())
+ .productName(product.getName())
+ .productThumbnail(product.getThumbnail())
+ .productAddress(productAddress.getAddress())
+ .productDetailAddress(productAddress.getDetailAddress())
+ .roomId(room.getId())
+ .roomName(room.getName())
+ .standard(room.getStandard())
+ .capacity(room.getCapacity())
+ .checkIn(DateTimeUtil.toString(room.getCheckIn()))
+ .checkOut(DateTimeUtil.toString(room.getCheckOut()))
+ .price(PricePickerByDateUtil.getPrice(room))
.build();
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/star/constant/StarValidationConstants.java b/src/main/java/com/fc/shimpyo_be/domain/star/constant/StarValidationConstants.java
new file mode 100644
index 00000000..9813d0a4
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/star/constant/StarValidationConstants.java
@@ -0,0 +1,20 @@
+package com.fc.shimpyo_be.domain.star.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StarValidationConstants {
+
+ // validation constraint value
+ public static final String SCORE_DECIMAL_MIN_VALUE = "0.0";
+ public static final String SCORE_DECIMAL_MAX_VALUE = "5.0";
+ public static final int SCORE_DIGITS_INTEGER_VALUE = 1;
+ public static final int SCORE_DIGITS_FRACTION_VALUE = 1;
+
+ // validation message
+ public static final String STAR_PRODUCTID_NOTNULL_MESSAGE = "별점 등록 대상 숙소 아이디는 필수값입니다.";
+ public static final String SCORE_DECIMAL_MIN_MESSAGE = "별점은 최소 0.0점 미만일 수 없습니다.";
+ public static final String SCORE_DECIMAL_MAX_MESSAGE = "별점은 최대 5.0점을 초과할 수 없습니다.";
+ public static final String SCORE_DIGITS_MESSAGE = "별점은 정수 1자리, 소수점 1자리까지만 가능합니다.";
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/star/controller/StarRestController.java b/src/main/java/com/fc/shimpyo_be/domain/star/controller/StarRestController.java
index 641be44b..3f97c659 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/star/controller/StarRestController.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/star/controller/StarRestController.java
@@ -10,10 +10,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
@Slf4j
@RequiredArgsConstructor
@@ -26,7 +23,6 @@ public class StarRestController {
@PostMapping
public ResponseEntity> register(@Valid @RequestBody StarRegisterRequestDto request) {
- log.info("[api][POST] /api/stars");
return ResponseEntity
.status(HttpStatus.CREATED)
diff --git a/src/main/java/com/fc/shimpyo_be/domain/star/dto/request/StarRegisterRequestDto.java b/src/main/java/com/fc/shimpyo_be/domain/star/dto/request/StarRegisterRequestDto.java
index f66c7699..07c3e74c 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/star/dto/request/StarRegisterRequestDto.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/star/dto/request/StarRegisterRequestDto.java
@@ -5,14 +5,17 @@
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
+import static com.fc.shimpyo_be.domain.star.constant.StarValidationConstants.*;
+
public record StarRegisterRequestDto(
@NotNull
Long reservationProductId,
- @NotNull(message = "별점 등록 대상 숙소 아이디는 필수값입니다.")
+ @NotNull(message = STAR_PRODUCTID_NOTNULL_MESSAGE)
Long productId,
- @DecimalMax(value = "5.0", message = "별점은 최대 5.0점을 초과할 수 없습니다.")
- @DecimalMin(value = "0.0", message = "별점은 최소 0.0점 미만일 수 없습니다.")
- @Digits(integer = 1, fraction = 1, message = "별점은 정수 1자리, 소수점 1자리까지만 가능합니다.")
+ @DecimalMax(value = SCORE_DECIMAL_MAX_VALUE, message = SCORE_DECIMAL_MAX_MESSAGE)
+ @DecimalMin(value = SCORE_DECIMAL_MIN_VALUE, message = SCORE_DECIMAL_MIN_MESSAGE)
+ @Digits(integer = SCORE_DIGITS_INTEGER_VALUE, fraction = SCORE_DIGITS_FRACTION_VALUE,
+ message = SCORE_DIGITS_MESSAGE)
float score
) {
}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/star/entity/Star.java b/src/main/java/com/fc/shimpyo_be/domain/star/entity/Star.java
index 7e357b38..a64ecbef 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/star/entity/Star.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/star/entity/Star.java
@@ -14,6 +14,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Comment;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@@ -22,18 +23,22 @@ public class Star {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Comment(value = "별점 식별자")
private Long id;
+ @Comment(value = "회원 식별자")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
+ @Comment(value = "별점 등록 숙소 식별자")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private Product product;
+ @Comment(value = "별점 점수")
@Column(nullable = false)
private float score;
@Builder
- public Star(Long id, Member member, Product product, float score) {
+ private Star(Long id, Member member, Product product, float score) {
this.id = id;
this.member = member;
this.product = product;
diff --git a/src/main/java/com/fc/shimpyo_be/domain/star/exception/ExpiredRegisterDateException.java b/src/main/java/com/fc/shimpyo_be/domain/star/exception/ExpiredRegisterDateException.java
new file mode 100644
index 00000000..ae8bcfee
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/domain/star/exception/ExpiredRegisterDateException.java
@@ -0,0 +1,11 @@
+package com.fc.shimpyo_be.domain.star.exception;
+
+import com.fc.shimpyo_be.global.exception.ApplicationException;
+import com.fc.shimpyo_be.global.exception.ErrorCode;
+
+public class ExpiredRegisterDateException extends ApplicationException {
+
+ public ExpiredRegisterDateException() {
+ super(ErrorCode.EXPIRED_STAR_REGISTER_DATE);
+ }
+}
diff --git a/src/main/java/com/fc/shimpyo_be/domain/star/service/StarService.java b/src/main/java/com/fc/shimpyo_be/domain/star/service/StarService.java
index 32d3742d..baeb4fff 100644
--- a/src/main/java/com/fc/shimpyo_be/domain/star/service/StarService.java
+++ b/src/main/java/com/fc/shimpyo_be/domain/star/service/StarService.java
@@ -1,8 +1,7 @@
package com.fc.shimpyo_be.domain.star.service;
import com.fc.shimpyo_be.domain.member.entity.Member;
-import com.fc.shimpyo_be.domain.member.exception.MemberNotFoundException;
-import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
+import com.fc.shimpyo_be.domain.member.service.MemberService;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.exception.ProductNotFoundException;
import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
@@ -13,6 +12,7 @@
import com.fc.shimpyo_be.domain.star.dto.response.StarResponseDto;
import com.fc.shimpyo_be.domain.star.entity.Star;
import com.fc.shimpyo_be.domain.star.exception.CannotBeforeCheckOutException;
+import com.fc.shimpyo_be.domain.star.exception.ExpiredRegisterDateException;
import com.fc.shimpyo_be.domain.star.repository.StarRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -29,36 +29,27 @@
public class StarService {
private final StarRepository starRepository;
- private final MemberRepository memberRepository;
+ private final MemberService memberService;
private final ProductRepository productRepository;
private final ReservationProductRepository reservationProductRepository;
@Transactional
public StarResponseDto register(Long memberId, StarRegisterRequestDto request) {
- log.debug("{} ::: {}", getClass().getSimpleName(), "register");
- // 회원 조회
- Member member = memberRepository.findById(memberId)
- .orElseThrow(MemberNotFoundException::new);
+ Member member = memberService.getMemberById(memberId);
- // 예약 상품 조회
ReservationProduct reservationProduct = reservationProductRepository.findByIdWithRoom(request.reservationProductId())
.orElseThrow(ReservationProductNotFoundException::new);
- // 별점 등록 가능 기간인지 검증
validateRegisterDate(LocalDateTime.now(), reservationProduct.getEndDate(), reservationProduct.getRoom().getCheckOut());
- // 상품 조회
Product product = productRepository.findById(request.productId())
.orElseThrow(ProductNotFoundException::new);
- // 별점 총 수 카운트
long total = starRepository.countByProduct(product);
- // 별점 평균 업데이트
product.updateStarAvg(calculateStarAvg(product.getStarAvg(), total, request.score()));
- // 별점 등록
return new StarResponseDto(
starRepository.save(
Star.builder()
@@ -77,11 +68,19 @@ private float calculateStarAvg(float currentAvg, long total, float score) {
}
private void validateRegisterDate(LocalDateTime current, LocalDate endDate, LocalTime checkOut) {
- LocalDateTime target = LocalDateTime.of(endDate, checkOut);
+ LocalDateTime startDateTime = LocalDateTime.of(endDate, checkOut);
- if (current.isEqual(target) || current.isBefore(target)) {
+ if (current.isEqual(startDateTime) || current.isBefore(startDateTime)) {
log.error("별점 등록은 체크아웃 이후에 가능합니다.");
throw new CannotBeforeCheckOutException();
}
+
+ LocalDateTime endDateTime =
+ LocalDateTime.of(endDate.plusDays(14), LocalTime.of(23, 59, 59));
+
+ if (current.isAfter(endDateTime)) {
+ log.error("별점 등록은 체크아웃 후 2주 이내에만 가능합니다.");
+ throw new ExpiredRegisterDateException();
+ }
}
}
diff --git a/src/main/java/com/fc/shimpyo_be/global/exception/ErrorCode.java b/src/main/java/com/fc/shimpyo_be/global/exception/ErrorCode.java
index ff996420..ca992834 100644
--- a/src/main/java/com/fc/shimpyo_be/global/exception/ErrorCode.java
+++ b/src/main/java/com/fc/shimpyo_be/global/exception/ErrorCode.java
@@ -17,33 +17,38 @@ public enum ErrorCode {
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."),
WRONG_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 틀렸습니다."),
- // 상품
- PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "상품 정보를 찾을 수 없습니다."),
+ // 숙소
+ PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "숙소 정보를 찾을 수 없습니다."),
ROOM_NOT_RESERVE(HttpStatus.FORBIDDEN, "예약 불가능한 방입니다."),
ROON_NOT_FOUND(HttpStatus.NOT_FOUND, "방 정보를 찾을 수 없습니다."),
// 예약
UNAVAILABLE_ROOMS(HttpStatus.BAD_REQUEST, "예약 불가능한 방이 존재합니다."),
LOCK_FAIL(HttpStatus.BAD_REQUEST, "요청 완료에 실패했습니다. 재시도가 필요합니다."),
- INVALID_RESERVATION_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 예약 요청 데이터입니다."),
+ RESERVATION_VALIDATION_FAIL(HttpStatus.BAD_REQUEST, "예약 선점 기한이 만료되었거나, 잘못된 예약 요청으로 예약이 불가합니다."),
- // 예약 상품
- RESERVATION_PRODUCT_NOT_FOUND(HttpStatus.BAD_REQUEST, "예약 상품 정보를 찾을 수 없습니다."),
- FORBIDDEN_CANCEL_RESERVATION_PRODUCT(HttpStatus.FORBIDDEN, "예약 상품을 취소할 권한이 없습니다."),
+ // 예약 숙소
+ RESERVATION_PRODUCT_NOT_FOUND(HttpStatus.BAD_REQUEST, "예약 숙소 정보를 찾을 수 없습니다."),
+ FORBIDDEN_CANCEL_RESERVATION_PRODUCT(HttpStatus.FORBIDDEN, "예약 숙소을 취소할 권한이 없습니다."),
// 장바구니
CART_NOT_FOUND(HttpStatus.NOT_FOUND, "장바구니 정보를 찾을 수 없습니다."),
CART_NOT_DELETE(HttpStatus.FORBIDDEN, "해당 장바구니를 삭제할 권한이 없습니다"),
// 별점
- REGISTER_BEFORE_CHECKOUT(HttpStatus.BAD_REQUEST, "별점 등록 가능 기간이 아닙니다."),
+ REGISTER_BEFORE_CHECKOUT(HttpStatus.BAD_REQUEST, "별점 등록은 체크아웃 이후에 가능합니다."),
+ EXPIRED_STAR_REGISTER_DATE(HttpStatus.BAD_REQUEST, "별점 등록 가능 기간이 만료되었습니다.(체크아웃 후 2주 이내)"),
// Open API
HTTP_CLIENT_CONNECTION_ERROR(HttpStatus.UNAUTHORIZED, "외부 API 연결에 실패했습니다."),
OPEN_API_ERROR(HttpStatus.NOT_FOUND, "오픈 API에서 데이터를 불러오는데 실패했습니다."),
- //Common
- INVALID_DATE(HttpStatus.BAD_REQUEST,"잘못된 날짜 데이터입니다.");
+ // Common
+ INVALID_DATE(HttpStatus.BAD_REQUEST,"잘못된 날짜 데이터입니다."),
+
+ // 즐겨찾기
+ FAVORITE_ALREADY_REGISTER(HttpStatus.BAD_REQUEST, "이미 즐겨찾기에 등록한 숙소입니다."),
+ FAVORITE_NOT_FOUND(HttpStatus.NOT_FOUND, "즐겨찾기 정보를 찾을 수 없습니다.");
private final HttpStatus httpStatus;
private final String simpleMessage;
diff --git a/src/main/java/com/fc/shimpyo_be/global/util/DateTimeUtil.java b/src/main/java/com/fc/shimpyo_be/global/util/DateTimeUtil.java
index dfe0921c..5c1c9069 100644
--- a/src/main/java/com/fc/shimpyo_be/global/util/DateTimeUtil.java
+++ b/src/main/java/com/fc/shimpyo_be/global/util/DateTimeUtil.java
@@ -1,6 +1,7 @@
package com.fc.shimpyo_be.global.util;
import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@@ -8,6 +9,7 @@ public class DateTimeUtil {
public final static String LOCAL_DATE_PATTERN = "yyyy-MM-dd";
public final static String LOCAL_TIME_PATTERN = "HH:mm";
+ public final static String LOCAL_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public final static String LOCAL_DATE_REGEX_PATTERN = "\\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])";
@@ -24,6 +26,10 @@ public static String toString(LocalTime timeObject) {
return timeObject.format(DateTimeFormatter.ofPattern(LOCAL_TIME_PATTERN));
}
+ public static String toString(LocalDateTime dateTimeObject) {
+ return dateTimeObject.format(DateTimeFormatter.ofPattern(LOCAL_DATETIME_PATTERN));
+ }
+
public static LocalDate toLocalDate(String dateString) {
return LocalDate.parse(dateString, DateTimeFormatter.ofPattern(LOCAL_DATE_PATTERN));
}
diff --git a/src/main/java/com/fc/shimpyo_be/global/util/PricePickerByDateUtil.java b/src/main/java/com/fc/shimpyo_be/global/util/PricePickerByDateUtil.java
new file mode 100644
index 00000000..4757d960
--- /dev/null
+++ b/src/main/java/com/fc/shimpyo_be/global/util/PricePickerByDateUtil.java
@@ -0,0 +1,51 @@
+package com.fc.shimpyo_be.global.util;
+
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import java.time.LocalDate;
+
+public class PricePickerByDateUtil {
+
+ private final static LocalDate today = LocalDate.now();
+
+
+ public static long getPrice(Room room) {
+
+ boolean isPeakTime = isPeakTime();
+ boolean isWeekend = isWeekend();
+ if (isPeakTime && isWeekend) {
+ return room.getPrice().getPeakWeekendMinFee();
+ } else if (isPeakTime && !isWeekend) {
+ return room.getPrice().getPeakWeekDaysMinFee();
+ } else if (!isPeakTime && isWeekend) {
+ return room.getPrice().getOffWeekendMinFee();
+ } else {
+ return room.getPrice().getOffWeekDaysMinFee();
+ }
+ }
+
+
+ public static boolean isWeekend() {
+ boolean isWeekend = false;
+
+ isWeekend = switch (today.getDayOfWeek().getValue()) {
+ case 6, 7 -> true;
+ default -> isWeekend;
+ };
+
+ return isWeekend;
+
+ }
+
+ public static boolean isPeakTime() {
+ boolean isPeakMonth = true;
+
+ isPeakMonth = switch (today.getMonth()) {
+ //성수기
+ case MARCH, APRIL, MAY, JUNE, SEPTEMBER -> false;
+ default -> isPeakMonth;
+ };
+
+ return isPeakMonth;
+ }
+
+}
diff --git a/src/main/java/com/fc/shimpyo_be/global/util/SecurityUtil.java b/src/main/java/com/fc/shimpyo_be/global/util/SecurityUtil.java
index 2f92fb1d..88624c91 100644
--- a/src/main/java/com/fc/shimpyo_be/global/util/SecurityUtil.java
+++ b/src/main/java/com/fc/shimpyo_be/global/util/SecurityUtil.java
@@ -15,4 +15,14 @@ public Long getCurrentMemberId() {
}
return Long.parseLong(authentication.getName());
}
+
+ public Long getNullableCurrentMemberId() {
+ final Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (authentication == null || authentication.getName() == null || authentication.getName().equals("anonymousUser")) {
+ return null;
+ }
+
+ return Long.parseLong(authentication.getName());
+ }
}
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
deleted file mode 100644
index 12e3355b..00000000
--- a/src/main/resources/application.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-spring:
- profiles:
- default: prod
diff --git a/src/main/resources/image/cart-docs.png b/src/main/resources/image/cart-docs.png
deleted file mode 100644
index 92d3c3b1..00000000
Binary files a/src/main/resources/image/cart-docs.png and /dev/null differ
diff --git a/src/main/resources/image/docs-error.png b/src/main/resources/image/docs-error.png
new file mode 100644
index 00000000..7b4465d7
Binary files /dev/null and b/src/main/resources/image/docs-error.png differ
diff --git a/src/main/resources/image/docs/cart-docs.png b/src/main/resources/image/docs/cart-docs.png
new file mode 100644
index 00000000..8bce70a1
Binary files /dev/null and b/src/main/resources/image/docs/cart-docs.png differ
diff --git a/src/main/resources/image/docs/favorite-docs.png b/src/main/resources/image/docs/favorite-docs.png
new file mode 100644
index 00000000..dd3f9974
Binary files /dev/null and b/src/main/resources/image/docs/favorite-docs.png differ
diff --git a/src/main/resources/image/index-docs.png b/src/main/resources/image/docs/index-docs.png
similarity index 100%
rename from src/main/resources/image/index-docs.png
rename to src/main/resources/image/docs/index-docs.png
diff --git a/src/main/resources/image/docs/member-docs.png b/src/main/resources/image/docs/member-docs.png
new file mode 100644
index 00000000..7f8508f7
Binary files /dev/null and b/src/main/resources/image/docs/member-docs.png differ
diff --git a/src/main/resources/image/docs/product-docs.png b/src/main/resources/image/docs/product-docs.png
new file mode 100644
index 00000000..c76c4ba0
Binary files /dev/null and b/src/main/resources/image/docs/product-docs.png differ
diff --git a/src/main/resources/image/docs/reservation-docs.png b/src/main/resources/image/docs/reservation-docs.png
new file mode 100644
index 00000000..4fbe06cc
Binary files /dev/null and b/src/main/resources/image/docs/reservation-docs.png differ
diff --git a/src/main/resources/image/docs/reservation-product-docs.png b/src/main/resources/image/docs/reservation-product-docs.png
new file mode 100644
index 00000000..1b26250f
Binary files /dev/null and b/src/main/resources/image/docs/reservation-product-docs.png differ
diff --git a/src/main/resources/image/docs/room-docs.png b/src/main/resources/image/docs/room-docs.png
new file mode 100644
index 00000000..db0832d7
Binary files /dev/null and b/src/main/resources/image/docs/room-docs.png differ
diff --git a/src/main/resources/image/docs/star-docs.png b/src/main/resources/image/docs/star-docs.png
new file mode 100644
index 00000000..08db15b6
Binary files /dev/null and b/src/main/resources/image/docs/star-docs.png differ
diff --git a/src/main/resources/image/erd.png b/src/main/resources/image/erd.png
index 8c04e67c..67056c54 100644
Binary files a/src/main/resources/image/erd.png and b/src/main/resources/image/erd.png differ
diff --git a/src/main/resources/image/main-page.png b/src/main/resources/image/main-page.png
new file mode 100644
index 00000000..13340c86
Binary files /dev/null and b/src/main/resources/image/main-page.png differ
diff --git a/src/main/resources/image/member-docs.png b/src/main/resources/image/member-docs.png
deleted file mode 100644
index 16fd7f5b..00000000
Binary files a/src/main/resources/image/member-docs.png and /dev/null differ
diff --git a/src/main/resources/image/product-docs.png b/src/main/resources/image/product-docs.png
deleted file mode 100644
index 17af6ec0..00000000
Binary files a/src/main/resources/image/product-docs.png and /dev/null differ
diff --git a/src/main/resources/image/reservation-docs.png b/src/main/resources/image/reservation-docs.png
deleted file mode 100644
index 1183fb08..00000000
Binary files a/src/main/resources/image/reservation-docs.png and /dev/null differ
diff --git a/src/main/resources/image/reservation-product-docs.png b/src/main/resources/image/reservation-product-docs.png
deleted file mode 100644
index 10351bf0..00000000
Binary files a/src/main/resources/image/reservation-product-docs.png and /dev/null differ
diff --git a/src/main/resources/image/star-docs.png b/src/main/resources/image/star-docs.png
deleted file mode 100644
index 92a3f61e..00000000
Binary files a/src/main/resources/image/star-docs.png and /dev/null differ
diff --git a/src/main/resources/image/wrapper-error-1.png b/src/main/resources/image/wrapper-error-1.png
new file mode 100644
index 00000000..ce58b621
Binary files /dev/null and b/src/main/resources/image/wrapper-error-1.png differ
diff --git a/src/main/resources/image/wrapper-error-2.png b/src/main/resources/image/wrapper-error-2.png
new file mode 100644
index 00000000..1fc49f8a
Binary files /dev/null and b/src/main/resources/image/wrapper-error-2.png differ
diff --git a/src/test/java/com/fc/shimpyo_be/config/DatabaseCleanUp.java b/src/test/java/com/fc/shimpyo_be/config/DatabaseCleanUp.java
new file mode 100644
index 00000000..f48bf516
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/config/DatabaseCleanUp.java
@@ -0,0 +1,58 @@
+package com.fc.shimpyo_be.config;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Profile;
+import org.springframework.transaction.annotation.Transactional;
+import org.testcontainers.shaded.com.google.common.base.CaseFormat;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+@Profile("test")
+public class DatabaseCleanUp implements InitializingBean {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ private List tableNames;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ tableNames = entityManager.getMetamodel().getEntities().stream()
+ .filter(e -> e.getJavaType().getAnnotation(Entity.class) != null)
+ .map(e -> CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, e.getName()))
+ .collect(Collectors.toList());
+ }
+
+ @Transactional
+ public void cleanUp() {
+ entityManager.flush();
+
+ entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
+ for (String tableName : tableNames) {
+ entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate();
+ entityManager
+ .createNativeQuery("ALTER TABLE " + tableName + " ALTER COLUMN id RESTART WITH 1")
+ .executeUpdate();
+ }
+ entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
+ }
+
+ @Transactional
+ public void cleanUp(String[] tables) {
+ entityManager.flush();
+
+ entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
+ for (String table : tables) {
+ entityManager.createNativeQuery("TRUNCATE TABLE " + table).executeUpdate();
+ entityManager
+ .createNativeQuery("ALTER TABLE " + table + " ALTER COLUMN id RESTART WITH 1")
+ .executeUpdate();
+ }
+ entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/config/TestDBCleanerConfig.java b/src/test/java/com/fc/shimpyo_be/config/TestDBCleanerConfig.java
new file mode 100644
index 00000000..31925369
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/config/TestDBCleanerConfig.java
@@ -0,0 +1,13 @@
+package com.fc.shimpyo_be.config;
+
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+
+@TestConfiguration
+public class TestDBCleanerConfig {
+
+ @Bean
+ public DatabaseCleanUp databaseCleanUp() {
+ return new DatabaseCleanUp();
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/cart/docs/CartRestIntegrationDocsTest.java b/src/test/java/com/fc/shimpyo_be/domain/cart/docs/CartRestIntegrationDocsTest.java
index dca45b9d..3f56e12e 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/cart/docs/CartRestIntegrationDocsTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/cart/docs/CartRestIntegrationDocsTest.java
@@ -26,20 +26,22 @@
import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
import com.fc.shimpyo_be.domain.room.entity.Room;
import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
+import com.fc.shimpyo_be.global.util.DateTimeUtil;
import com.fc.shimpyo_be.global.util.SecurityUtil;
+import java.time.LocalDate;
import java.util.concurrent.ThreadLocalRandom;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.ResultActions;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
-@AutoConfigureMockMvc
+
class CartRestIntegrationDocsTest extends RestDocsSupport {
@MockBean
@@ -68,19 +70,15 @@ class CartRestIntegrationDocsTest extends RestDocsSupport {
@BeforeEach
void initTest() {
//given
- member = memberRepository.save(memberRepository.save(Member.builder()
+ member = memberRepository.save(Member.builder()
.email("wocjf" + ThreadLocalRandom.current().nextInt(100000) + "@naver.com")
.photoUrl("hello,world.jpg").name("심재철").password("1234").authority(Authority.ROLE_USER)
- .build()));
+ .build());
given(securityUtil.getCurrentMemberId()).willReturn(1L);
product = productRepository.save(ProductFactory.createTestProduct());
- room = roomRepository.save(ProductFactory.createTestRoom(product));
-
- for (int i = 0; i < 5; i++) {
- Cart cart = cartRepository.save(CartFactory.createCartTest(room, member));
- }
+ room = roomRepository.save(ProductFactory.createTestRoom(product,0L));
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
@@ -89,17 +87,23 @@ void initTest() {
@WithMockUser
void getCarts() throws Exception {
//given
+ for (int i = 0; i < 5; i++) {
+ Cart cart = cartRepository.save(CartFactory.createCartTest(room, member));
+ }
+
+ given(securityUtil.getCurrentMemberId()).willReturn(member.getId());
//when
ResultActions resultActions = mockMvc.perform(get("/api/carts"));
+
//then
resultActions.andExpect(status().isOk()).andDo(restDoc.document(
responseFields(responseCommon()).and(
fieldWithPath("data").type(JsonFieldType.ARRAY).description("응답 데이터"),
fieldWithPath("data[].cartId").type(JsonFieldType.NUMBER).description("장바구니 아이디"),
- fieldWithPath("data[].productId").type(JsonFieldType.NUMBER).description("상품 아이디"),
- fieldWithPath("data[].productName").type(JsonFieldType.STRING).description("상품 이름"),
- fieldWithPath("data[].image").type(JsonFieldType.STRING).description("상품 대표 이미지"),
- fieldWithPath("data[].roomId").type(JsonFieldType.NUMBER).description("방 아이디"),
+ fieldWithPath("data[].productId").type(JsonFieldType.NUMBER).description("숙소 아이디"),
+ fieldWithPath("data[].productName").type(JsonFieldType.STRING).description("숙소 이름"),
+ fieldWithPath("data[].image").type(JsonFieldType.STRING).description("숙소 대표 이미지"),
+ fieldWithPath("data[].roomCode").type(JsonFieldType.NUMBER).description("방 코드"),
fieldWithPath("data[].roomName").type(JsonFieldType.STRING).description("방 이름"),
fieldWithPath("data[].price").type(JsonFieldType.NUMBER).description("총 가격"),
fieldWithPath("data[].description").type(JsonFieldType.STRING).description("방 설명"),
@@ -109,18 +113,21 @@ void getCarts() throws Exception {
fieldWithPath("data[].endDate").type(JsonFieldType.STRING).description("숙박 종료일"),
fieldWithPath("data[].checkIn").type(JsonFieldType.STRING).description("방 체크인 시간"),
fieldWithPath("data[].checkOut").type(JsonFieldType.STRING)
- .description("방 체크아웃 시간"),
- fieldWithPath("data[].reserved").type(JsonFieldType.BOOLEAN)
- .description("예약 가능 여부"))));
-
+ .description("방 체크아웃 시간"))));
}
@Test
@WithMockUser
void addCart() throws Exception {
//given
- CartCreateRequest cartCreateRequest = CartCreateRequest.builder().startDate("2023-12-27")
- .endDate("2023-12-30").price(100000L).roomId(room.getId()).build();
+ LocalDate tommorrow = LocalDate.now().plusDays(1);
+ CartCreateRequest cartCreateRequest = CartCreateRequest.builder()
+ .startDate(DateTimeUtil.toString(
+ tommorrow))
+ .endDate(DateTimeUtil.toString(tommorrow.plusDays(2))).price(100000L)
+ .roomCode(room.getCode()).build();
+
+ cartRepository.save(Cart.builder().roomCode(0L).price(10000L).member(member).startDate(tommorrow.plusDays(2)).endDate(tommorrow.plusDays(3)).build());
//when
ResultActions resultActions = mockMvc.perform(
post("/api/carts").content(objectMapper.writeValueAsString(cartCreateRequest))
@@ -128,17 +135,17 @@ void addCart() throws Exception {
//then
resultActions.andExpect(status().isOk()).andDo(restDoc.document(
- requestFields(fieldWithPath("roomId").type(JsonFieldType.NUMBER).description("방 아이디"),
+ requestFields(fieldWithPath("roomCode").type(JsonFieldType.NUMBER).description("방 코드"),
fieldWithPath("startDate").type(JsonFieldType.STRING).description("숙박 시작일"),
fieldWithPath("endDate").type(JsonFieldType.STRING).description("숙박 종료일"),
fieldWithPath("price").type(JsonFieldType.NUMBER).description("장바구니 가격")),
responseFields(responseCommon()).and(
fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
fieldWithPath("data.cartId").type(JsonFieldType.NUMBER).description("장바구니 아이디"),
- fieldWithPath("data.productId").type(JsonFieldType.NUMBER).description("상품 아이디"),
- fieldWithPath("data.productName").type(JsonFieldType.STRING).description("상품 이름"),
- fieldWithPath("data.image").type(JsonFieldType.STRING).description("상품 대표 이미지"),
- fieldWithPath("data.roomId").type(JsonFieldType.NUMBER).description("방 아이디"),
+ fieldWithPath("data.productId").type(JsonFieldType.NUMBER).description("숙소 아이디"),
+ fieldWithPath("data.productName").type(JsonFieldType.STRING).description("숙소 이름"),
+ fieldWithPath("data.image").type(JsonFieldType.STRING).description("숙소 대표 이미지"),
+ fieldWithPath("data.roomCode").type(JsonFieldType.NUMBER).description("방 코드"),
fieldWithPath("data.roomName").type(JsonFieldType.STRING).description("방 이름"),
fieldWithPath("data.price").type(JsonFieldType.NUMBER).description("총 가격"),
fieldWithPath("data.description").type(JsonFieldType.STRING).description("방 설명"),
@@ -149,9 +156,7 @@ void addCart() throws Exception {
fieldWithPath("data.checkIn").type(JsonFieldType.STRING).description("방 체크인 시간"),
fieldWithPath("data.checkOut").type(JsonFieldType.STRING).description("방 체크아웃 시간"),
fieldWithPath(("data.checkOut")).type(JsonFieldType.STRING)
- .description("방 체크아웃 시간"),
- fieldWithPath("data.reserved").type(JsonFieldType.BOOLEAN)
- .description("예약 가능 여부"))));
+ .description("방 체크아웃 시간"))));
}
@@ -168,7 +173,7 @@ void deleteCart() throws Exception {
responseFields(responseCommon()).and(
fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
fieldWithPath("data.cartId").type(JsonFieldType.NUMBER).description("장바구니 아이디"),
- fieldWithPath("data.roomId").type(JsonFieldType.NUMBER).description("방 아이디"),
+ fieldWithPath("data.roomCode").type(JsonFieldType.NUMBER).description("방 코드"),
fieldWithPath("data.startDate").type(JsonFieldType.STRING).description("숙박 시작일"),
fieldWithPath("data.endDate").type(JsonFieldType.STRING).description("숙박 종료일"))));
}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/cart/factory/CartFactory.java b/src/test/java/com/fc/shimpyo_be/domain/cart/factory/CartFactory.java
index 907907ee..5310f3f4 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/cart/factory/CartFactory.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/cart/factory/CartFactory.java
@@ -9,7 +9,7 @@
public class CartFactory {
public static Cart createCartTest(Room room, Member member) {
- return Cart.builder().room(room).member(member).price(
+ return Cart.builder().roomCode(room.getCode()).member(member).price(
100000L
).startDate(LocalDate.now().plusDays(1))
.endDate(LocalDate.now().plusDays(2)).build();
diff --git a/src/test/java/com/fc/shimpyo_be/domain/cart/unit/controller/CartRestControllerTest.java b/src/test/java/com/fc/shimpyo_be/domain/cart/unit/controller/CartRestControllerTest.java
index 1b6fffe1..22bc47b3 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/cart/unit/controller/CartRestControllerTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/cart/unit/controller/CartRestControllerTest.java
@@ -50,11 +50,11 @@ public class CartRestControllerTest extends AbstractContainersSupport {
public static void initTest() {
//given
Product product = ProductFactory.createTestProduct();
- room = ProductFactory.createTestRoom(product);
+ room = ProductFactory.createTestRoom(product,0l);
member = Member.builder().email("wocjf0513@naver.com").photoUrl("hello,world.jpg")
.name("심재철").password("1234").authority(Authority.ROLE_USER).build();
cart = CartFactory.createCartTest(room, member);
- cartResponse = CartMapper.toCartResponse(cart);
+ cartResponse = CartMapper.toCartResponse(cart,room);
cartResponses.add(cartResponse);
}
@@ -73,7 +73,7 @@ void successToGetCarts() {
@Test
void successToAddCart() {
//given
- CartCreateRequest cartCreateRequest = CartCreateRequest.builder().roomId(room.getId()).price(cartResponse.getPrice())
+ CartCreateRequest cartCreateRequest = CartCreateRequest.builder().roomCode(room.getCode()).price(cartResponse.getPrice())
.startDate(cartResponse.getStartDate()).endDate(cartResponse.getEndDate())
.build();
diff --git a/src/test/java/com/fc/shimpyo_be/domain/cart/unit/service/CartRestServiceTest.java b/src/test/java/com/fc/shimpyo_be/domain/cart/unit/service/CartRestServiceTest.java
index fad49302..545484c8 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/cart/unit/service/CartRestServiceTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/cart/unit/service/CartRestServiceTest.java
@@ -2,6 +2,8 @@
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doReturn;
@@ -10,6 +12,7 @@
import com.fc.shimpyo_be.domain.cart.dto.response.CartResponse;
import com.fc.shimpyo_be.domain.cart.entity.Cart;
import com.fc.shimpyo_be.domain.cart.factory.CartFactory;
+import com.fc.shimpyo_be.domain.cart.repository.CartCustomRepositoryImpl;
import com.fc.shimpyo_be.domain.cart.repository.CartRepository;
import com.fc.shimpyo_be.domain.cart.service.CartService;
import com.fc.shimpyo_be.domain.cart.util.CartMapper;
@@ -23,6 +26,7 @@
import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
import com.fc.shimpyo_be.global.util.DateTimeUtil;
import com.fc.shimpyo_be.global.util.SecurityUtil;
+import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -36,7 +40,6 @@
@ExtendWith(MockitoExtension.class)
public class CartRestServiceTest {
- private static CartResponse cartResponse;
private static Room room;
private static Member member;
private static Cart cart;
@@ -50,6 +53,9 @@ public class CartRestServiceTest {
private RoomRepository roomRepository;
@Mock
private CartRepository cartRepository;
+
+ @Mock
+ private CartCustomRepositoryImpl cartCustomRepository;
@Mock
private SecurityUtil securityUtil;
@InjectMocks
@@ -60,23 +66,22 @@ public class CartRestServiceTest {
public static void initTest() {
//given
Product product = ProductFactory.createTestProduct();
- room = ProductFactory.createTestRoom(product);
+ room = ProductFactory.createTestRoom(product, 0l);
member = Member.builder().id(1L).email("wocjf0513@naver.com").photoUrl("hello,world.jpg")
.name("심재철").password("1234").authority(Authority.ROLE_USER).build();
cart = CartFactory.createCartTest(room, member);
- cartResponse = CartMapper.toCartResponse(cart);
}
@Test
void SuccessToGetCarts() {
//given
+
List carts = new ArrayList<>();
carts.add(cart);
- List expectedCartResponses = carts.stream().map(CartMapper::toCartResponse)
+ List expectedCartResponses = carts.stream()
+ .map(cartEntity -> CartMapper.toCartResponse(cartEntity, room))
.toList();
- doReturn(true).when(productService)
- .isAvailableForReservation(cart.getId(), DateTimeUtil.toString(cart.getStartDate()),
- DateTimeUtil.toString(cart.getEndDate()));
+ given(roomRepository.findByCode(cart.getRoomCode())).willReturn(List.of(room));
given(cartRepository.findByMemberId(1L)).willReturn(Optional.of(carts));
given(securityUtil.getCurrentMemberId()).willReturn(1L);
//when
@@ -90,17 +95,20 @@ void SuccessToGetCarts() {
void SuccessToAddCart() {
//given
- CartCreateRequest cartCreateRequest = CartCreateRequest.builder().roomId(room.getId())
+ CartCreateRequest cartCreateRequest = CartCreateRequest.builder().roomCode(room.getCode())
.price(100000L).startDate("2023-11-27").endDate("2023-11-28").build();
- doReturn(true).when(productService)
- .isAvailableForReservation(cartCreateRequest.roomId(), cartCreateRequest.startDate(),
- cartCreateRequest.endDate());
- Cart expectedCart = CartMapper.toCart(cartCreateRequest, room, member);
- CartResponse expectedCartResponse = CartMapper.toCartResponse(expectedCart);
+ Cart expectedCart = CartMapper.toCart(cartCreateRequest, member);
+ CartResponse expectedCartResponse = CartMapper.toCartResponse(expectedCart, room);
given(cartRepository.save(any())).willReturn(expectedCart);
+ given(cartCustomRepository.countByRoomCodeAndMemberIdContainsDate(any(),
+ anyLong())).willReturn(0L);
given(securityUtil.getCurrentMemberId()).willReturn(member.getId());
- given(roomRepository.findById(room.getId())).willReturn(Optional.ofNullable(room));
+ given(roomRepository.findByCode(cartCreateRequest.roomCode())).willReturn(List.of(room));
given(memberRepository.findById(member.getId())).willReturn(Optional.ofNullable(member));
+ doReturn(1L).when(productService)
+ .countAvailableForReservationUsingRoomCode(anyLong(),
+ anyString(),
+ anyString());
//when
CartResponse result = cartService.addCart(cartCreateRequest);
//then
diff --git a/src/test/java/com/fc/shimpyo_be/domain/favorite/docs/FavoriteRestControllerDocsTest.java b/src/test/java/com/fc/shimpyo_be/domain/favorite/docs/FavoriteRestControllerDocsTest.java
new file mode 100644
index 00000000..1c99e2f0
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/domain/favorite/docs/FavoriteRestControllerDocsTest.java
@@ -0,0 +1,160 @@
+package com.fc.shimpyo_be.domain.favorite.docs;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
+import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
+import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.fc.shimpyo_be.config.RestDocsSupport;
+import com.fc.shimpyo_be.domain.favorite.dto.FavoriteResponseDto;
+import com.fc.shimpyo_be.domain.favorite.dto.FavoritesResponseDto;
+import com.fc.shimpyo_be.domain.favorite.service.FavoriteService;
+import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
+import com.fc.shimpyo_be.domain.product.entity.Category;
+import com.fc.shimpyo_be.global.util.SecurityUtil;
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.data.domain.Pageable;
+import org.springframework.restdocs.payload.JsonFieldType;
+import org.springframework.security.test.context.support.WithMockUser;
+
+public class FavoriteRestControllerDocsTest extends RestDocsSupport {
+
+ @MockBean
+ private FavoriteService favoriteService;
+
+ @MockBean
+ SecurityUtil securityUtil;
+
+ @Test
+ @DisplayName("register()은 즐겨찾기를 등록할 수 있다.")
+ @WithMockUser(roles = "USER")
+ void register() throws Exception {
+ // given
+ FavoriteResponseDto favoriteResponseDto = FavoriteResponseDto.builder()
+ .favoriteId(1L)
+ .memberId(1L)
+ .productId(1L)
+ .build();
+
+ given(securityUtil.getCurrentMemberId()).willReturn(1L);
+ given(favoriteService.register(any(Long.TYPE), any(Long.TYPE)))
+ .willReturn(favoriteResponseDto);
+
+ // when then
+ mockMvc.perform(post("/api/favorites/{productId}", 1L))
+ .andExpect(status().isCreated())
+ .andDo(restDoc.document(
+ responseFields(responseCommon()).and(
+ fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
+ fieldWithPath("data.favoriteId").type(JsonFieldType.NUMBER)
+ .description("즐겨찾기 식별자"),
+ fieldWithPath("data.memberId").type(JsonFieldType.NUMBER).description("회원 식별자"),
+ fieldWithPath("data.productId").type(JsonFieldType.NUMBER).description("숙소 식별자")
+ ))
+ );
+ }
+
+
+ @Test
+ @DisplayName("getFavorites()은 즐겨찾기 목록을 조회할 수 있다.")
+ @WithMockUser(roles = "USER")
+ void getFavorites() throws Exception {
+ // given
+ FavoritesResponseDto favoritesResponseDto = FavoritesResponseDto.builder()
+ .pageCount(10)
+ .products(List.of(ProductResponse.builder()
+ .productId(1L)
+ .productName("OO 호텔")
+ .category(Category.TOURIST_HOTEL.getName())
+ .address("서울시 강남구 OO로 000-000 상세주소")
+ .favorites(true)
+ .image(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .starAvg(5F)
+ .price(95000L)
+ .capacity(4L)
+ .build()))
+ .build();
+
+ given(securityUtil.getCurrentMemberId()).willReturn(1L);
+ given(favoriteService.getFavorites(any(Long.TYPE), any(Pageable.class)))
+ .willReturn(favoritesResponseDto);
+
+ // when then
+ mockMvc.perform(get("/api/favorites")
+ .queryParam("page", "0")
+ .queryParam("size", "10"))
+ .andExpect(status().isOk())
+ .andDo(restDoc.document(
+ queryParameters(parameterWithName("page").optional().description("페이지 인덱스"),
+ parameterWithName("size").optional().description("페이지 사이즈")
+ ),
+ responseFields(responseCommon()).and(
+ fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
+ fieldWithPath("data.pageCount").type(JsonFieldType.NUMBER)
+ .description("총 페이지 개수"),
+ fieldWithPath("data.products").type(JsonFieldType.ARRAY)
+ .description("숙소 응답 데이터 배열"),
+ fieldWithPath("data.products[].productId").type(
+ JsonFieldType.NUMBER)
+ .description("숙소 아이디"),
+ fieldWithPath("data.products[].category").type(JsonFieldType.STRING)
+ .description("숙소 카테고리(호텔, 모텔, 풀빌라, 펜션)"),
+ fieldWithPath("data.products[].address").type(JsonFieldType.STRING)
+ .description("숙소 주소"),
+ fieldWithPath("data.products[].productName").type(
+ JsonFieldType.STRING)
+ .description("숙소 이름"),
+ fieldWithPath("data.products[].favorites").type(
+ JsonFieldType.BOOLEAN)
+ .description("즐겨찾기"),
+ fieldWithPath("data.products[].starAvg").type(JsonFieldType.NUMBER)
+ .description("숙소 평점"),
+ fieldWithPath("data.products[].image").type(JsonFieldType.STRING)
+ .description("숙소 썸네일 이미지"),
+ fieldWithPath("data.products[].price").type(JsonFieldType.NUMBER)
+ .description("숙소 내 방 최저 가격"),
+ fieldWithPath("data.products[].capacity").type(JsonFieldType.NUMBER)
+ .description("최대 인원")
+ ))
+ );
+ }
+
+ @Test
+ @DisplayName("cancel은 즐겨찾기를 취소할 수 있다.")
+ @WithMockUser(roles = "USER")
+ void cancel() throws Exception {
+ // given
+ FavoriteResponseDto favoriteResponseDto = FavoriteResponseDto.builder()
+ .favoriteId(1L)
+ .memberId(1L)
+ .productId(1L)
+ .build();
+
+ given(securityUtil.getCurrentMemberId()).willReturn(1L);
+ given(favoriteService.delete(any(Long.TYPE), any(Long.TYPE)))
+ .willReturn(favoriteResponseDto);
+
+ // when then
+ mockMvc.perform(delete("/api/favorites/{productId}", 1L))
+ .andExpect(status().isOk())
+ .andDo(restDoc.document(
+ responseFields(responseCommon()).and(
+ fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
+ fieldWithPath("data.favoriteId").type(JsonFieldType.NUMBER)
+ .description("즐겨찾기 식별자"),
+ fieldWithPath("data.memberId").type(JsonFieldType.NUMBER).description("회원 식별자"),
+ fieldWithPath("data.productId").type(JsonFieldType.NUMBER).description("숙소 식별자")
+ ))
+ );
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/favorite/unit/controller/FavoriteRestControllerTest.java b/src/test/java/com/fc/shimpyo_be/domain/favorite/unit/controller/FavoriteRestControllerTest.java
new file mode 100644
index 00000000..456fe806
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/domain/favorite/unit/controller/FavoriteRestControllerTest.java
@@ -0,0 +1,173 @@
+package com.fc.shimpyo_be.domain.favorite.unit.controller;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.fc.shimpyo_be.config.AbstractContainersSupport;
+import com.fc.shimpyo_be.domain.favorite.dto.FavoriteResponseDto;
+import com.fc.shimpyo_be.domain.favorite.dto.FavoritesResponseDto;
+import com.fc.shimpyo_be.domain.favorite.service.FavoriteService;
+import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
+import com.fc.shimpyo_be.domain.product.entity.Category;
+import com.fc.shimpyo_be.global.util.SecurityUtil;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.data.domain.Pageable;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+public class FavoriteRestControllerTest extends AbstractContainersSupport {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ WebApplicationContext context;
+
+ @MockBean
+ FavoriteService favoriteService;
+
+ @MockBean
+ SecurityUtil securityUtil;
+
+ @BeforeEach
+ public void setup() {
+ mockMvc = MockMvcBuilders
+ .webAppContextSetup(this.context)
+ .apply(springSecurity())
+ .build();
+ }
+
+ @Nested
+ @DisplayName("register()은")
+ class Context_register {
+
+ @Test
+ @DisplayName("즐겨찾기를 등록할 수 있다.")
+ @WithMockUser(roles = "USER")
+ void _willSuccess() throws Exception {
+ // given
+ FavoriteResponseDto favoriteResponseDto = FavoriteResponseDto.builder()
+ .favoriteId(1L)
+ .memberId(1L)
+ .productId(1L)
+ .build();
+
+ given(securityUtil.getCurrentMemberId()).willReturn(1L);
+ given(favoriteService.register(any(Long.TYPE), any(Long.TYPE)))
+ .willReturn(favoriteResponseDto);
+
+ // when then
+ mockMvc.perform(post("/api/favorites/{productId}", 1L))
+ .andExpect(status().isCreated())
+ .andExpect(jsonPath("$.code").isNumber())
+ .andExpect(jsonPath("$.message").isString())
+ .andExpect(jsonPath("$.data").isMap())
+ .andExpect(jsonPath("$.data.favoriteId").isNumber())
+ .andExpect(jsonPath("$.data.memberId").isNumber())
+ .andExpect(jsonPath("$.data.productId").isNumber())
+ .andDo(print());
+ }
+ }
+
+ @Nested
+ @DisplayName("getFavorites()은")
+ class Context_getFavorites {
+
+ @Test
+ @DisplayName("즐겨찾기 목록을 조회할 수 있다.")
+ @WithMockUser(roles = "USER")
+ void _willSuccess() throws Exception {
+ // given
+ FavoritesResponseDto favoritesResponseDto = FavoritesResponseDto.builder()
+ .pageCount(10)
+ .products(List.of(ProductResponse.builder()
+ .productId(1L)
+ .productName("OO 호텔")
+ .category(Category.TOURIST_HOTEL.getName())
+ .address("서울시 강남구 OO로 000-000 상세주소")
+ .favorites(true)
+ .image(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .starAvg(5F)
+ .price(95000L)
+ .capacity(4L)
+ .build()))
+ .build();
+
+ given(securityUtil.getCurrentMemberId()).willReturn(1L);
+ given(favoriteService.getFavorites(any(Long.TYPE), any(Pageable.class)))
+ .willReturn(favoritesResponseDto);
+
+ // when then
+ mockMvc.perform(get("/api/favorites")
+ .queryParam("page", "0")
+ .queryParam("size", "10"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").isNumber())
+ .andExpect(jsonPath("$.message").isString())
+ .andExpect(jsonPath("$.data").isMap())
+ .andExpect(jsonPath("$.data.pageCount").isNumber())
+ .andExpect(jsonPath("$.data.products[0].productId").isNumber())
+ .andExpect(jsonPath("$.data.products[0].productName").isString())
+ .andExpect(jsonPath("$.data.products[0].category").isString())
+ .andExpect(jsonPath("$.data.products[0].address").isString())
+ .andExpect(jsonPath("$.data.products[0].favorites").isBoolean())
+ .andExpect(jsonPath("$.data.products[0].image").isString())
+ .andExpect(jsonPath("$.data.products[0].starAvg").isNumber())
+ .andExpect(jsonPath("$.data.products[0].price").isNumber())
+ .andExpect(jsonPath("$.data.products[0].capacity").isNumber())
+ .andDo(print());
+ }
+ }
+
+ @Nested
+ @DisplayName("cancel()은")
+ class Context_cancel {
+
+ @Test
+ @DisplayName("즐겨찾기를 취소할 수 있다.")
+ @WithMockUser(roles = "USER")
+ void _willSuccess() throws Exception {
+ // given
+ FavoriteResponseDto favoriteResponseDto = FavoriteResponseDto.builder()
+ .favoriteId(1L)
+ .memberId(1L)
+ .productId(1L)
+ .build();
+
+ given(securityUtil.getCurrentMemberId()).willReturn(1L);
+ given(favoriteService.delete(any(Long.TYPE), any(Long.TYPE)))
+ .willReturn(favoriteResponseDto);
+
+ // when then
+ mockMvc.perform(delete("/api/favorites/{productId}", 1L))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code").isNumber())
+ .andExpect(jsonPath("$.message").isString())
+ .andExpect(jsonPath("$.data").isMap())
+ .andExpect(jsonPath("$.data.favoriteId").isNumber())
+ .andExpect(jsonPath("$.data.memberId").isNumber())
+ .andExpect(jsonPath("$.data.productId").isNumber())
+ .andDo(print());
+ }
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/favorite/unit/repository/FavoriteRepositoryTest.java b/src/test/java/com/fc/shimpyo_be/domain/favorite/unit/repository/FavoriteRepositoryTest.java
new file mode 100644
index 00000000..835eba1b
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/domain/favorite/unit/repository/FavoriteRepositoryTest.java
@@ -0,0 +1,182 @@
+package com.fc.shimpyo_be.domain.favorite.unit.repository;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.fc.shimpyo_be.config.TestJpaConfig;
+import com.fc.shimpyo_be.config.TestQuerydslConfig;
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
+import com.fc.shimpyo_be.domain.favorite.repository.FavoriteRepository;
+import com.fc.shimpyo_be.domain.member.entity.Authority;
+import com.fc.shimpyo_be.domain.member.entity.Member;
+import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
+import com.fc.shimpyo_be.domain.product.entity.Address;
+import com.fc.shimpyo_be.domain.product.entity.Amenity;
+import com.fc.shimpyo_be.domain.product.entity.Category;
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import com.fc.shimpyo_be.domain.product.entity.ProductOption;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import java.util.ArrayList;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
+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.Pageable;
+
+@DataJpaTest
+@Import({TestJpaConfig.class, TestQuerydslConfig.class})
+@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
+public class FavoriteRepositoryTest {
+
+ @Autowired
+ private FavoriteRepository favoriteRepository;
+
+ @Autowired
+ private MemberRepository memberRepository;
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @BeforeEach
+ public void reset() {
+ entityManager.flush();
+ entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
+ favoriteRepository.deleteAll();
+ memberRepository.deleteAll();
+ productRepository.deleteAll();
+ entityManager.createNativeQuery("TRUNCATE TABLE favorite").executeUpdate();
+ entityManager.createNativeQuery("TRUNCATE TABLE member").executeUpdate();
+ entityManager.createNativeQuery("TRUNCATE TABLE product").executeUpdate();
+ entityManager
+ .createNativeQuery("ALTER TABLE favorite ALTER COLUMN `id` RESTART WITH 1")
+ .executeUpdate();
+ entityManager
+ .createNativeQuery("ALTER TABLE member ALTER COLUMN `id` RESTART WITH 1")
+ .executeUpdate();
+ entityManager
+ .createNativeQuery("ALTER TABLE product ALTER COLUMN `id` RESTART WITH 1")
+ .executeUpdate();
+ entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
+ }
+
+ private Member saveMember() {
+ Member member = Member.builder()
+ .email("test@mail.com")
+ .name("test")
+ .password("$10$ygrAExVYmFTkZn2d0.Pk3Ot5CNZwIBjZH5f.WW0AnUq4w4PtBi9Nm")
+ .photoUrl(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .authority(Authority.ROLE_USER)
+ .build();
+ return memberRepository.save(member);
+ }
+
+ private Product saveProduct() {
+ Product product = Product.builder()
+ .id(1L)
+ .name("OO 호텔")
+ .address(Address.builder()
+ .address("서울시 강남구 OO로 000-000")
+ .detailAddress("상세주소")
+ .mapX(1.0)
+ .mapY(1.0)
+ .build())
+ .thumbnail(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .category(Category.TOURIST_HOTEL)
+ .starAvg(5)
+ .description("호텔입니다.")
+ .rooms(new ArrayList<>())
+ .productOption(ProductOption.builder()
+ .cooking(false)
+ .parking(false)
+ .pickup(false)
+ .foodPlace("")
+ .infoCenter("000-0000-0000")
+ .build())
+ .amenity(Amenity.builder()
+ .barbecue(false)
+ .beauty(false)
+ .beverage(false)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(false)
+ .publicBath(false)
+ .publicPc(false)
+ .sauna(false)
+ .seminar(false)
+ .sports(false)
+ .fitness(false)
+ .build())
+ .build();
+ return productRepository.save(product);
+ }
+
+ private void saveFavorite(Member member, Product product) {
+ Favorite favorite = Favorite.builder()
+ .member(member)
+ .product(product)
+ .build();
+ favoriteRepository.save(favorite);
+ }
+
+ @Nested
+ @DisplayName("findByMemberAndProduct()는")
+ class Context_findByMemberAndProduct {
+
+ @Test
+ @DisplayName("회원과 숙소로 즐겨찾기 정보를 조회할 수 있다.")
+ void _willSuccess() {
+ // given
+ Member member = saveMember();
+ Product product = saveProduct();
+ saveFavorite(member, product);
+
+ // when
+ Optional result = favoriteRepository.findByMemberAndProduct(member, product);
+
+ // then
+ assertThat(result.isPresent()).isTrue();
+ assertThat(result.get().getId()).isNotNull();
+ assertThat(result.get().getMember().getId()).isEqualTo(member.getId());
+ assertThat(result.get().getProduct().getId()).isEqualTo(product.getId());
+ }
+ }
+
+ @Nested
+ @DisplayName("findAllByMemberId()는")
+ class Context_findAllByMemberId {
+
+ @Test
+ @DisplayName("회원으로 즐겨찾기 정보 목록을 조회할 수 있다.")
+ void _willSuccess() {
+ // given
+ Member member = saveMember();
+ Product product = saveProduct();
+ saveFavorite(member, product);
+ Pageable pageable = org.springframework.data.domain.PageRequest.of(0, 10);
+
+ // when
+ Page result = favoriteRepository.findAllByMemberId(member.getId(), pageable);
+
+ // then
+ assertThat(result.isEmpty()).isFalse();
+ assertThat(result.getTotalPages()).isEqualTo(1);
+ assertThat(result.get().toList().get(0).getId()).isNotNull();
+ assertThat(result.get().toList().get(0).getMember().getId()).isEqualTo(member.getId());
+ assertThat(result.get().toList().get(0).getProduct().getId()).isEqualTo(
+ product.getId());
+ }
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/favorite/unit/service/FavoriteServiceTest.java b/src/test/java/com/fc/shimpyo_be/domain/favorite/unit/service/FavoriteServiceTest.java
new file mode 100644
index 00000000..355e5fe8
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/domain/favorite/unit/service/FavoriteServiceTest.java
@@ -0,0 +1,295 @@
+package com.fc.shimpyo_be.domain.favorite.unit.service;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import com.fc.shimpyo_be.domain.favorite.dto.FavoriteResponseDto;
+import com.fc.shimpyo_be.domain.favorite.dto.FavoritesResponseDto;
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
+import com.fc.shimpyo_be.domain.favorite.repository.FavoriteRepository;
+import com.fc.shimpyo_be.domain.favorite.service.FavoriteService;
+import com.fc.shimpyo_be.domain.member.entity.Authority;
+import com.fc.shimpyo_be.domain.member.entity.Member;
+import com.fc.shimpyo_be.domain.member.service.MemberService;
+import com.fc.shimpyo_be.domain.product.entity.Address;
+import com.fc.shimpyo_be.domain.product.entity.Amenity;
+import com.fc.shimpyo_be.domain.product.entity.Category;
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import com.fc.shimpyo_be.domain.product.entity.ProductOption;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+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.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+@ExtendWith(MockitoExtension.class)
+public class FavoriteServiceTest {
+
+ @InjectMocks
+ private FavoriteService favoriteService;
+
+ @Mock
+ private FavoriteRepository favoriteRepository;
+
+ @Mock
+ private MemberService memberService;
+
+ @Mock
+ private ProductRepository productRepository;
+
+ @Nested
+ @DisplayName("register()은")
+ class Context_register {
+
+ @Test
+ @DisplayName("즐겨찾기를 등록할 수 있다.")
+ void _willSuccess() {
+ // given
+ Member member = Member.builder()
+ .id(1L)
+ .email("test@mail.com")
+ .name("test")
+ .password("$10$ygrAExVYmFTkZn2d0.Pk3Ot5CNZwIBjZH5f.WW0AnUq4w4PtBi9Nm")
+ .photoUrl(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .authority(Authority.ROLE_USER)
+ .build();
+ Product product = Product.builder()
+ .id(1L)
+ .name("OO 호텔")
+ .address(Address.builder()
+ .address("서울시 강남구 OO로 000-000")
+ .detailAddress("상세주소")
+ .mapX(1.0)
+ .mapY(1.0)
+ .build())
+ .thumbnail(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .category(Category.TOURIST_HOTEL)
+ .starAvg(5)
+ .description("호텔입니다.")
+ .rooms(new ArrayList<>())
+ .productOption(ProductOption.builder()
+ .cooking(false)
+ .parking(false)
+ .pickup(false)
+ .foodPlace("")
+ .infoCenter("000-0000-0000")
+ .build())
+ .amenity(Amenity.builder()
+ .barbecue(false)
+ .beauty(false)
+ .beverage(false)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(false)
+ .publicBath(false)
+ .publicPc(false)
+ .sauna(false)
+ .seminar(false)
+ .sports(false)
+ .fitness(false)
+ .build())
+ .build();
+ Favorite favorite = Favorite.builder()
+ .id(1L)
+ .member(member)
+ .product(product)
+ .build();
+
+ given(memberService.getMemberById(any(Long.TYPE))).willReturn(member);
+ given(productRepository.findById(any(Long.TYPE))).willReturn(Optional.of(product));
+ given(favoriteRepository.findByMemberAndProduct(any(Member.class), any(Product.class)))
+ .willReturn(Optional.empty());
+ given(favoriteRepository.save(any(Favorite.class))).willReturn(favorite);
+
+ // when
+ FavoriteResponseDto result = favoriteService.register(1L, 1L);
+
+ // then
+ assertNotNull(result);
+ assertThat(result).extracting("favoriteId", "memberId", "productId")
+ .containsExactly(1L, 1L, 1L);
+
+ verify(memberService, times(1)).getMemberById(any(Long.TYPE));
+ verify(productRepository, times(1)).findById(any(Long.TYPE));
+ verify(favoriteRepository, times(1))
+ .findByMemberAndProduct(any(Member.class), any(Product.class));
+ verify(favoriteRepository, times(1)).save(any(Favorite.class));
+ }
+ }
+
+ @Nested
+ @DisplayName("getFavorites()은")
+ class Context_getFavorites {
+
+ @Test
+ @DisplayName("즐겨찾기 목록을 조회할 수 있다.")
+ void _willSuccess() {
+ // given
+ Member member = Member.builder()
+ .id(1L)
+ .email("test@mail.com")
+ .name("test")
+ .password("$10$ygrAExVYmFTkZn2d0.Pk3Ot5CNZwIBjZH5f.WW0AnUq4w4PtBi9Nm")
+ .photoUrl(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .authority(Authority.ROLE_USER)
+ .build();
+ Product product = Product.builder()
+ .id(1L)
+ .name("OO 호텔")
+ .address(Address.builder()
+ .address("서울시 강남구 OO로 000-000")
+ .detailAddress("상세주소")
+ .mapX(1.0)
+ .mapY(1.0)
+ .build())
+ .thumbnail(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .category(Category.TOURIST_HOTEL)
+ .starAvg(5)
+ .description("호텔입니다.")
+ .rooms(new ArrayList<>())
+ .productOption(ProductOption.builder()
+ .cooking(false)
+ .parking(false)
+ .pickup(false)
+ .foodPlace("")
+ .infoCenter("000-0000-0000")
+ .build())
+ .amenity(Amenity.builder()
+ .barbecue(false)
+ .beauty(false)
+ .beverage(false)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(false)
+ .publicBath(false)
+ .publicPc(false)
+ .sauna(false)
+ .seminar(false)
+ .sports(false)
+ .fitness(false)
+ .build())
+ .build();
+ Favorite favorite = Favorite.builder()
+ .id(1L)
+ .member(member)
+ .product(product)
+ .build();
+ Pageable pageable = org.springframework.data.domain.PageRequest.of(0, 10);
+
+ given(memberService.getMemberById(any(Long.TYPE))).willReturn(member);
+ given(favoriteRepository.findAllByMemberId(any(Long.class), any(Pageable.class)))
+ .willReturn(new PageImpl<>(List.of(favorite)));
+
+ // when
+ FavoritesResponseDto result = favoriteService.getFavorites(1L, pageable);
+
+ // then
+ assertNotNull(result);
+
+ verify(memberService, times(1)).getMemberById(any(Long.TYPE));
+ verify(favoriteRepository, times(1)).findAllByMemberId(any(Long.class),
+ any(Pageable.class));
+ }
+ }
+
+ @Nested
+ @DisplayName("delete()은")
+ class Context_delete {
+
+ @Test
+ @DisplayName("즐겨찾기를 취소할 수 있다.")
+ void _willSuccess() {
+ // given
+ Member member = Member.builder()
+ .id(1L)
+ .email("test@mail.com")
+ .name("test")
+ .password("$10$ygrAExVYmFTkZn2d0.Pk3Ot5CNZwIBjZH5f.WW0AnUq4w4PtBi9Nm")
+ .photoUrl(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .authority(Authority.ROLE_USER)
+ .build();
+ Product product = Product.builder()
+ .id(1L)
+ .name("OO 호텔")
+ .address(Address.builder()
+ .address("서울시 강남구 OO로 000-000")
+ .detailAddress("상세주소")
+ .mapX(1.0)
+ .mapY(1.0)
+ .build())
+ .thumbnail(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .category(Category.TOURIST_HOTEL)
+ .starAvg(5)
+ .description("호텔입니다.")
+ .rooms(new ArrayList<>())
+ .productOption(ProductOption.builder()
+ .cooking(false)
+ .parking(false)
+ .pickup(false)
+ .foodPlace("")
+ .infoCenter("000-0000-0000")
+ .build())
+ .amenity(Amenity.builder()
+ .barbecue(false)
+ .beauty(false)
+ .beverage(false)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(false)
+ .publicBath(false)
+ .publicPc(false)
+ .sauna(false)
+ .seminar(false)
+ .sports(false)
+ .fitness(false)
+ .build())
+ .build();
+ Favorite favorite = Favorite.builder()
+ .id(1L)
+ .member(member)
+ .product(product)
+ .build();
+
+ given(memberService.getMemberById(any(Long.TYPE))).willReturn(member);
+ given(productRepository.findById(any(Long.TYPE))).willReturn(Optional.of(product));
+ given(favoriteRepository.findByMemberAndProduct(any(Member.class), any(Product.class)))
+ .willReturn(Optional.of(favorite));
+ doNothing().when(favoriteRepository).delete(any(Favorite.class));
+
+ // when
+ FavoriteResponseDto result = favoriteService.delete(1L, 1L);
+
+ // then
+ assertNotNull(result);
+ assertThat(result).extracting("favoriteId", "memberId", "productId")
+ .containsExactly(1L, 1L, 1L);
+
+ verify(memberService, times(1)).getMemberById(any(Long.TYPE));
+ verify(productRepository, times(1)).findById(any(Long.TYPE));
+ verify(favoriteRepository, times(1))
+ .findByMemberAndProduct(any(Member.class), any(Product.class));
+ verify(favoriteRepository, times(1)).delete(any(Favorite.class));
+ }
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/member/docs/AuthRestControllerDocsTest.java b/src/test/java/com/fc/shimpyo_be/domain/member/docs/AuthRestControllerDocsTest.java
index b64be10f..e4350f54 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/member/docs/AuthRestControllerDocsTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/member/docs/AuthRestControllerDocsTest.java
@@ -34,9 +34,9 @@ public class AuthRestControllerDocsTest extends RestDocsSupport {
private final ConstraintDescriptions signUpDescriptions = new ConstraintDescriptions(
SignUpRequestDto.class);
private final ConstraintDescriptions signInDescriptions = new ConstraintDescriptions(
- SignUpRequestDto.class);
+ SignInRequestDto.class);
private final ConstraintDescriptions refreshDescriptions = new ConstraintDescriptions(
- SignUpRequestDto.class);
+ RefreshRequestDto.class);
@Test
@DisplayName("signUp()은 회원 가입 할 수 있다.")
@@ -57,7 +57,7 @@ void signUp() throws Exception {
given(authService.signUp(any(SignUpRequestDto.class))).willReturn(memberResponseDto);
- // when
+ // when then
mockMvc.perform(post("/api/auth/signup")
.content(objectMapper.writeValueAsString(signUpRequestDto))
.contentType(MediaType.APPLICATION_JSON))
@@ -118,7 +118,7 @@ void signIn() throws Exception {
given(authService.signIn(any(SignInRequestDto.class))).willReturn(signInResponseDto);
- // when
+ // when then
mockMvc.perform(post("/api/auth/signin")
.content(objectMapper.writeValueAsString(signInRequestDto))
.contentType(MediaType.APPLICATION_JSON))
@@ -187,7 +187,7 @@ void refresh() throws Exception {
given(authService.refresh(any(RefreshRequestDto.class))).willReturn(signInResponseDto);
- // when
+ // when then
mockMvc.perform(post("/api/auth/refresh")
.content(objectMapper.writeValueAsString(refreshRequestDto))
.contentType(MediaType.APPLICATION_JSON))
diff --git a/src/test/java/com/fc/shimpyo_be/domain/member/docs/MemberRestControllerDocsTest.java b/src/test/java/com/fc/shimpyo_be/domain/member/docs/MemberRestControllerDocsTest.java
index 1b6f71ed..5b8b8da9 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/member/docs/MemberRestControllerDocsTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/member/docs/MemberRestControllerDocsTest.java
@@ -1,5 +1,15 @@
package com.fc.shimpyo_be.domain.member.docs;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.doNothing;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
+import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
+import static org.springframework.restdocs.snippet.Attributes.key;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
+
import com.fc.shimpyo_be.config.RestDocsSupport;
import com.fc.shimpyo_be.domain.member.dto.request.CheckPasswordRequestDto;
import com.fc.shimpyo_be.domain.member.dto.request.UpdateMemberRequestDto;
@@ -15,14 +25,6 @@
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.doNothing;
-import static org.springframework.restdocs.payload.PayloadDocumentation.*;
-import static org.springframework.restdocs.snippet.Attributes.key;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
-
public class MemberRestControllerDocsTest extends RestDocsSupport {
@MockBean
diff --git a/src/test/java/com/fc/shimpyo_be/domain/product/docs/ProductRestIntegrationDocsTest.java b/src/test/java/com/fc/shimpyo_be/domain/product/docs/ProductRestIntegrationDocsTest.java
index b502a022..c033e407 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/product/docs/ProductRestIntegrationDocsTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/product/docs/ProductRestIntegrationDocsTest.java
@@ -1,5 +1,7 @@
package com.fc.shimpyo_be.domain.product.docs;
+import static org.junit.matchers.JUnitMatchers.everyItem;
+import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
@@ -10,28 +12,34 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.fc.shimpyo_be.config.RestDocsSupport;
-import com.fc.shimpyo_be.domain.product.dto.response.ProductDetailsResponse;
+import com.fc.shimpyo_be.domain.favorite.entity.Favorite;
+import com.fc.shimpyo_be.domain.favorite.repository.FavoriteRepository;
+import com.fc.shimpyo_be.domain.member.entity.Authority;
+import com.fc.shimpyo_be.domain.member.entity.Member;
+import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.entity.ProductImage;
import com.fc.shimpyo_be.domain.product.factory.ProductFactory;
import com.fc.shimpyo_be.domain.product.repository.ProductImageRepository;
import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
-import com.fc.shimpyo_be.domain.product.util.ProductMapper;
import com.fc.shimpyo_be.domain.room.entity.Room;
import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
+import com.fc.shimpyo_be.global.util.SecurityUtil;
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.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.restdocs.payload.JsonFieldType;
-import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
@AutoConfigureMockMvc
+@ActiveProfiles("test")
class ProductRestIntegrationDocsTest extends RestDocsSupport {
@@ -47,42 +55,28 @@ class ProductRestIntegrationDocsTest extends RestDocsSupport {
@Autowired
private RedisTemplate restTemplate;
+ @Autowired
+ private FavoriteRepository favoriteRepository;
- private void assertProductDetailsResponse(Product expectedProduct, ResultActions resultActions)
- throws Exception {
- // then
- ProductDetailsResponse expectedProductDetailsResponse = ProductMapper.toProductDetailsResponse(
- expectedProduct);
-
- resultActions.andDo(MockMvcResultHandlers.print()).andExpect(status().is2xxSuccessful())
- .andExpect(
- jsonPath("$.data.productId").value(expectedProductDetailsResponse.productId()))
- .andExpect(
- jsonPath("$.data.productName").value(expectedProductDetailsResponse.productName()))
- .andExpect(jsonPath("$.data.address").value(expectedProductDetailsResponse.address()))
- .andExpect(jsonPath("$.data.category").value(expectedProductDetailsResponse.category()))
- .andExpect(jsonPath("$.data.starAvg").value(expectedProductDetailsResponse.starAvg()));
-
-
- }
+ @Autowired
+ private MemberRepository memberRepository;
+ @MockBean
+ private SecurityUtil securityUtil;
@DisplayName("숙소 저장 후, 검색 조회 및 페이징할 수 있다.")
@Test
- @WithMockUser
void getProducts() throws Exception {
// given
- for (int i = 0; i < 20; i++) {
+ for (int i = 0; i < 5; i++) {
Product product = productRepository.save(ProductFactory.createTestProduct());
- ProductImage productImage = productImageRepository.save(
- ProductFactory.createTestProductImage(product));
- Room room = roomRepository.save(ProductFactory.createTestRoom(product));
+ Room room = roomRepository.save(ProductFactory.createTestRoom(product, 0L));
product.getRooms().add(room);
}
// when
ResultActions getProductAction = mockMvc.perform(
- get("/api/products?page=0&size=20&address=서울시&category=호텔,모텔&productName=숙박"));
+ get("/api/products?page=0&size=20&capacity=5"));
// then
getProductAction.andDo(MockMvcResultHandlers.print()).andExpect(status().isOk()).andDo(
@@ -90,35 +84,43 @@ void getProducts() throws Exception {
queryParameters(parameterWithName("page").optional().description("페이지 인덱스"),
parameterWithName("size").optional().description("페이지 사이즈"),
parameterWithName("sort").optional().description("정렬 할 컬럼 및 방향"),
- parameterWithName("address").optional().description("상품 주소"),
- parameterWithName("category").optional().description("상품 카테고리"),
- parameterWithName("productName").optional().description("상품 이름")),
+ parameterWithName("address").optional().description("숙소 주소"),
+ parameterWithName("category").optional().description("숙소 카테고리"),
+ parameterWithName("productName").optional().description("숙소 이름"),
+ parameterWithName("capacity").optional().description("객실 최대 수용인원")
+ ),
responseFields(
fieldWithPath("code").type(JsonFieldType.NUMBER).description("응답 코드"),
fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"),
- fieldWithPath("data").type(JsonFieldType.ARRAY).description("응답 데이터"),
- fieldWithPath("data[].productId").type(JsonFieldType.NUMBER)
- .description("상품 아이디"),
- fieldWithPath("data[].category").type(JsonFieldType.STRING)
- .description("상품 카테고리(호텔, 모텔, 풀빌라, 펜션)"),
- fieldWithPath("data[].address").type(JsonFieldType.STRING).description("상품 주소"),
- fieldWithPath("data[].productName").type(JsonFieldType.STRING)
- .description("상품 이름"),
- fieldWithPath("data[].favorites").type(JsonFieldType.BOOLEAN)
+ fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
+ fieldWithPath("data.productResponses").type(JsonFieldType.ARRAY)
+ .description("숙소 응답 데이터 배열"),
+ fieldWithPath("data.pageCount").type(JsonFieldType.NUMBER)
+ .description("총 페이지 개수"),
+ fieldWithPath("data.productResponses[].productId").type(JsonFieldType.NUMBER)
+ .description("숙소 아이디"),
+ fieldWithPath("data.productResponses[].category").type(JsonFieldType.STRING)
+ .description("숙소 카테고리(호텔, 모텔, 풀빌라, 펜션)"),
+ fieldWithPath("data.productResponses[].address").type(JsonFieldType.STRING)
+ .description("숙소 주소"),
+ fieldWithPath("data.productResponses[].productName").type(JsonFieldType.STRING)
+ .description("숙소 이름"),
+ fieldWithPath("data.productResponses[].favorites").type(JsonFieldType.BOOLEAN)
.description("즐겨찾기"),
- fieldWithPath("data[].starAvg").type(JsonFieldType.NUMBER).description("상품 평점"),
- fieldWithPath("data[].image").type(JsonFieldType.STRING)
- .description("상품 썸네일 이미지"),
- fieldWithPath("data[].price").type(JsonFieldType.NUMBER)
- .description("상품 내 방 최저 가격"),
- fieldWithPath("data[].capacity").type(JsonFieldType.NUMBER)
+ fieldWithPath("data.productResponses[].starAvg").type(JsonFieldType.NUMBER)
+ .description("숙소 평점"),
+ fieldWithPath("data.productResponses[].image").type(JsonFieldType.STRING)
+ .description("숙소 썸네일 이미지"),
+ fieldWithPath("data.productResponses[].price").type(JsonFieldType.NUMBER)
+ .description("숙소 내 방 최저 가격"),
+ fieldWithPath("data.productResponses[].capacity").type(JsonFieldType.NUMBER)
.description("최대 인원"))));
}
+
@DisplayName("숙소 상세 검색을 할 수 있다.")
@Test
- @WithMockUser
void getProductDetails() throws Exception {
// given
Product product = productRepository.save(ProductFactory.createTestProduct());
@@ -127,9 +129,14 @@ void getProductDetails() throws Exception {
ProductImage productImage2 = productImageRepository.save(
ProductFactory.createTestProductImage(product));
+ for (int i = 0; i < 5; i++) {
+ Room room = roomRepository.save(ProductFactory.createTestRoom(product, 0L));
+ product.getRooms().add(room);
+
+ }
for (int i = 0; i < 5; i++) {
- Room room = roomRepository.save(ProductFactory.createTestRoom(product));
+ Room room = roomRepository.save(ProductFactory.createTestRoom(product, 1L));
product.getRooms().add(room);
}
@@ -139,30 +146,128 @@ void getProductDetails() throws Exception {
product.getId()));
// then
- assertProductDetailsResponse(product, getProductAction);
- getProductAction.andDo(
- restDoc.document(pathParameters(parameterWithName("productId").description("상품 아이디")),
+ getProductAction.andDo(MockMvcResultHandlers.print()).andExpect(status().isOk()).andDo(
+ restDoc.document(pathParameters(parameterWithName("productId").description("숙소 아이디")),
queryParameters(parameterWithName("startDate").description("상세 검색, 체크인 일"),
parameterWithName("endDate").description("상세 검색, 체크아웃 일")), responseFields(
fieldWithPath("code").type(JsonFieldType.NUMBER).description("응답 코드"),
fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"),
fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
fieldWithPath("data.productId").type(JsonFieldType.NUMBER)
- .description("상품 아이디"),
+ .description("숙소 식별자"),
+ fieldWithPath("data.productAmenityResponse").type(JsonFieldType.OBJECT)
+ .description("숙소 어메니티"),
+ fieldWithPath("data.address").type(JsonFieldType.OBJECT)
+ .description("숙소 주소"),
+ fieldWithPath("data.address.address").type(JsonFieldType.STRING)
+ .description("숙소 주소"),
+ fieldWithPath("data.address.detailAddress").type(JsonFieldType.STRING)
+ .description("숙소 상세 주소"),
+ fieldWithPath("data.address.mapX").type(JsonFieldType.NUMBER)
+ .description("숙소 x좌표"),
+ fieldWithPath("data.address.mapY").type(JsonFieldType.NUMBER)
+ .description("숙소 y좌표"),
+ fieldWithPath("data.productAmenityResponse.barbecue").type(
+ JsonFieldType.BOOLEAN)
+ .description("숙소 바베큐장 여부"),
+ fieldWithPath("data.productAmenityResponse.beauty").type(JsonFieldType.BOOLEAN)
+ .description("숙소 뷰티시설 여부"),
+ fieldWithPath("data.productAmenityResponse.beverage").type(
+ JsonFieldType.BOOLEAN)
+ .description("숙소 식음료장 여부"),
+ fieldWithPath("data.productAmenityResponse.bicycle").type(JsonFieldType.BOOLEAN)
+ .description("숙소 자전거 대여 여부"),
+ fieldWithPath("data.productAmenityResponse.campfire").type(
+ JsonFieldType.BOOLEAN)
+ .description("숙소 캠프파이어 여부"),
+ fieldWithPath("data.productAmenityResponse.fitness").type(JsonFieldType.BOOLEAN)
+ .description("숙소 휘트니스 센터 여부"),
+ fieldWithPath("data.productAmenityResponse.karaoke").type(JsonFieldType.BOOLEAN)
+ .description("숙소 노래방 여부"),
+ fieldWithPath("data.productAmenityResponse.publicBath").type(
+ JsonFieldType.BOOLEAN)
+ .description("숙소 공동 샤워실 여부"),
+ fieldWithPath("data.productAmenityResponse.publicPc").type(
+ JsonFieldType.BOOLEAN)
+ .description("숙소 공동 PC실 여부"),
+ fieldWithPath("data.productAmenityResponse.sauna").type(JsonFieldType.BOOLEAN)
+ .description("숙소 사우나 시설 여부"),
+ fieldWithPath("data.productAmenityResponse.sports").type(JsonFieldType.BOOLEAN)
+ .description("숙소 스포츠 시설 여부"),
+ fieldWithPath("data.productAmenityResponse.seminar").type(JsonFieldType.BOOLEAN)
+ .description("숙소 세미나실 여부"),
+ fieldWithPath("data.productOptionResponse").type(JsonFieldType.OBJECT)
+ .description("숙소 옵션"),
+ fieldWithPath("data.productOptionResponse.cooking").type(JsonFieldType.BOOLEAN)
+ .description("객실 내 취사 여부"),
+ fieldWithPath("data.productOptionResponse.parking").type(JsonFieldType.BOOLEAN)
+ .description("숙소 주차 시설 여부"),
+ fieldWithPath("data.productOptionResponse.pickup").type(JsonFieldType.BOOLEAN)
+ .description("숙소 픽업 서비스 여부"),
+ fieldWithPath("data.productOptionResponse.foodPlace").type(JsonFieldType.STRING)
+ .description("숙소 식음료장"),
+ fieldWithPath("data.productOptionResponse.infoCenter").type(
+ JsonFieldType.STRING)
+ .description("숙소 문의 및 안내 번호"),
fieldWithPath("data.category").type(JsonFieldType.STRING)
- .description("상품 카테고리(호텔, 모텔, 풀빌라, 펜션)"),
- fieldWithPath("data.address").type(JsonFieldType.STRING).description("상품 주소"),
+ .description("숙소 카테고리(호텔, 모텔, 풀빌라, 펜션)"),
fieldWithPath("data.productName").type(JsonFieldType.STRING)
- .description("상품 이름"),
+ .description("숙소 이름"),
fieldWithPath("data.description").type(JsonFieldType.STRING)
- .description("상품 설명"),
+ .description("숙소 설명"),
fieldWithPath("data.favorites").type(JsonFieldType.BOOLEAN).description("즐겨찾기"),
- fieldWithPath("data.starAvg").type(JsonFieldType.NUMBER).description("상품 평점"),
- fieldWithPath("data.images").type(JsonFieldType.ARRAY).description("상품 관련 이미지"),
+ fieldWithPath("data.starAvg").type(JsonFieldType.NUMBER).description("숙소 평점"),
+ fieldWithPath("data.images").type(JsonFieldType.ARRAY).description("숙소 관련 이미지"),
fieldWithPath("data.rooms").type(JsonFieldType.ARRAY)
- .description("상품 하위 방 데이터"),
- fieldWithPath("data.rooms[].roomId").type(JsonFieldType.NUMBER)
- .description("방 아이디"),
+ .description("숙소 하위 방 데이터"),
+ fieldWithPath("data.rooms[].roomCode").type(JsonFieldType.NUMBER)
+ .description("객실 코드"),
+ fieldWithPath("data.rooms[].remaining").type(JsonFieldType.NUMBER)
+ .description("객실 남은수량"),
+ fieldWithPath("data.rooms[].roomImages[]").type(JsonFieldType.ARRAY)
+ .description("객실 이미지"),
+ fieldWithPath("data.rooms[].roomOptionResponse").type(JsonFieldType.OBJECT)
+ .description("방 옵션"),
+ fieldWithPath("data.rooms[].roomOptionResponse.bathFacility").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 목욕 시설 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.bath").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 욕조 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.homeTheater").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 홈시어터 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.airCondition").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 에어컨 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.tv").type(JsonFieldType.BOOLEAN)
+ .description("객실 내 TV 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.pc").type(JsonFieldType.BOOLEAN)
+ .description("객실 내 PC 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.cable").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 케이블 설치 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.internet").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 인터넷 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.refrigerator").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 냉장고 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.toiletries").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 세면도구 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.sofa").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 소파 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.cooking").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 취사용품 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.table").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 테이블 여부"),
+ fieldWithPath("data.rooms[].roomOptionResponse.hairDryer").type(
+ JsonFieldType.BOOLEAN)
+ .description("객실 내 드라이기 여부"),
fieldWithPath("data.rooms[].roomName").type(JsonFieldType.STRING)
.description("방 이름"),
fieldWithPath("data.rooms[].price").type(JsonFieldType.NUMBER)
@@ -176,26 +281,24 @@ void getProductDetails() throws Exception {
fieldWithPath("data.rooms[].checkIn").type(JsonFieldType.STRING)
.description("체크인 시간"),
fieldWithPath("data.rooms[].checkOut").type(JsonFieldType.STRING)
- .description("체크아웃 시간"),
- fieldWithPath("data.rooms[].reserved").type(JsonFieldType.BOOLEAN)
- .description("예약 여부"))));
+ .description("체크아웃 시간"))));
}
@Test
@DisplayName("예약 가능 여부를 확인할 수 있다.")
- @WithMockUser
void isAvailableForReservation() throws Exception {
// given
Product product = productRepository.save(ProductFactory.createTestProduct());
- Room room = roomRepository.save(ProductFactory.createTestRoom(product));
+ Room room = roomRepository.save(ProductFactory.createTestRoom(product, 0L));
product.getRooms().add(room);
ValueOperations values = restTemplate.opsForValue();
- values.set("roomId:" + String.valueOf(room.getId()) + ":" + "2023-12-22", "OK");
+ values.set("roomId:" + room.getId() + ":" + "2023-12-22", "OK");
// when
ResultActions getProductAction = mockMvc.perform(
- get("/api/products/amounts/{roomId}?startDate=2023-12-22&endDate=2023-12-23",room.getId()));
+ get("/api/products/amounts/{roomId}?startDate=2023-12-22&endDate=2023-12-23",
+ room.getId()));
// then
getProductAction
@@ -217,5 +320,34 @@ void isAvailableForReservation() throws Exception {
));
}
+ @Test
+ @DisplayName("전체 조회에서 즐겨찾기 여부를 볼 수 있다.")
+ void isAvailableGetFavoriteInGetProducts() throws Exception {
+ //given
+ given(securityUtil.getNullableCurrentMemberId()).willReturn(1L);
+ Product product = productRepository.save(ProductFactory.createTestProduct());
+ Room room = roomRepository.save(ProductFactory.createTestRoom(product, 0L));
+ Member member = Member.builder()
+ .email("test@mail.com")
+ .name("test")
+ .password("$10$ygrAExVYmFTkZn2d0.Pk3Ot5CNZwIBjZH5f.WW0AnUq4w4PtBi9Nm")
+ .photoUrl(
+ "https://fastly.picsum.photos/id/866/200/300.jpg?hmac=rcadCENKh4rD6MAp6V_ma-AyWv641M4iiOpe1RyFHeI")
+ .authority(Authority.ROLE_USER)
+ .build();
+ memberRepository.save(member);
+ Favorite favorite = Favorite.builder().product(product).member(member).build();
+ favoriteRepository.save(favorite);
+
+ // when
+ ResultActions getProductAction = mockMvc.perform(
+ get("/api/products"));
+
+ //then
+ getProductAction
+ .andDo(MockMvcResultHandlers.print()).andExpect(status().isOk()).andExpect(jsonPath("$.data.productResponses[0].favorites").value(true));
+
+ }
+
}
\ No newline at end of file
diff --git a/src/test/java/com/fc/shimpyo_be/domain/product/factory/ProductFactory.java b/src/test/java/com/fc/shimpyo_be/domain/product/factory/ProductFactory.java
index 4c67937a..226e5d40 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/product/factory/ProductFactory.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/product/factory/ProductFactory.java
@@ -1,14 +1,19 @@
package com.fc.shimpyo_be.domain.product.factory;
-import java.util.*;
+import com.fc.shimpyo_be.domain.product.entity.Address;
+import com.fc.shimpyo_be.domain.product.entity.Amenity;
import com.fc.shimpyo_be.domain.product.entity.Category;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.entity.ProductImage;
+import com.fc.shimpyo_be.domain.product.entity.ProductOption;
import com.fc.shimpyo_be.domain.product.model.Area;
import com.fc.shimpyo_be.domain.product.model.RandomProductInfo;
import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
import java.time.LocalTime;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class ProductFactory {
@@ -17,11 +22,39 @@ public class ProductFactory {
public static Product createTestProduct() {
String area = Area.values()[ThreadLocalRandom.current()
.nextInt(Area.values().length)].toString();
- return Product.builder().name(area + " 숙박").address("서울시" + area)
+ return Product.builder().name(area + " 숙박").address(Address.builder()
+ .address("서울시" + area)
+ .detailAddress("상세주소")
+ .mapX(1.0)
+ .mapY(1.0)
+ .build())
.thumbnail(RandomProductInfo.genRandomImage()).category(
Category.values()[ThreadLocalRandom.current().nextInt(Category.values().length)])
.starAvg(ThreadLocalRandom.current().nextFloat(5))
- .description(RandomProductInfo.genRandomDescription()).build();
+ .description(RandomProductInfo.genRandomDescription())
+ .rooms(new ArrayList<>())
+ .productOption(ProductOption.builder()
+ .cooking(false)
+ .parking(false)
+ .pickup(false)
+ .foodPlace("")
+ .infoCenter("000-0000-0000")
+ .build())
+ .amenity(Amenity.builder()
+ .barbecue(false)
+ .beauty(false)
+ .beverage(false)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(false)
+ .publicBath(false)
+ .publicPc(false)
+ .sauna(false)
+ .seminar(false)
+ .sports(false)
+ .fitness(false)
+ .build())
+ .build();
}
public static ProductImage createTestProductImage(Product product) {
@@ -29,15 +62,40 @@ public static ProductImage createTestProductImage(Product product) {
.build();
}
- public static Room createTestRoom(Product product) {
+ public static Room createTestRoom(Product product, Long code) {
int stadard = ThreadLocalRandom.current().nextInt(10);
+ int fee = ThreadLocalRandom.current().nextInt(100000);
- return Room.builder().price(ThreadLocalRandom.current().nextInt(100000))
+ return Room.builder().price(RoomPrice.builder()
+ .offWeekDaysMinFee(fee)
+ .offWeekendMinFee(fee + 10000)
+ .peakWeekDaysMinFee(fee + 50000)
+ .peakWeekendMinFee(fee + 60000)
+ .build())
.description(RandomProductInfo.genRandomDescription()).product(product)
.checkIn(LocalTime.of(11, 0, 0)).checkOut(LocalTime.of(15, 0, 0))
.name(product.getCategory().getName() + " 방").standard(stadard)
- .capacity(stadard + ThreadLocalRandom.current().nextInt(10)).build();
+ .capacity(stadard + ThreadLocalRandom.current().nextInt(10))
+ .roomImages(new ArrayList<>())
+ .code(code)
+ .roomOption(RoomOption.builder()
+ .bathFacility(false)
+ .bath(false)
+ .homeTheater(false)
+ .airCondition(false)
+ .tv(false)
+ .pc(false)
+ .cable(false)
+ .internet(false)
+ .refrigerator(false)
+ .toiletries(false)
+ .sofa(false)
+ .cooking(false)
+ .diningTable(false)
+ .hairDryer(false)
+ .build())
+ .build();
}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/product/unit/controller/ProductRestControllerTest.java b/src/test/java/com/fc/shimpyo_be/domain/product/unit/controller/ProductRestControllerTest.java
index 4893d6e6..b279d066 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/product/unit/controller/ProductRestControllerTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/product/unit/controller/ProductRestControllerTest.java
@@ -7,6 +7,7 @@
import com.fc.shimpyo_be.domain.product.controller.ProductRestController;
import com.fc.shimpyo_be.domain.product.dto.request.SearchKeywordRequest;
+import com.fc.shimpyo_be.domain.product.dto.response.PaginatedProductResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductDetailsResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
import com.fc.shimpyo_be.domain.product.entity.Category;
@@ -40,17 +41,25 @@ class ProductRestControllerTest {
void getAllProducts() {
//given
List productResponses = new ArrayList<>();
- productResponses.add(ProductMapper.toProductResponse(ProductFactory.createTestProduct()));
+ productResponses.add(ProductMapper.toProductResponse(ProductFactory.createTestProduct(),false));
+ PaginatedProductResponse paginatedProductResponse = PaginatedProductResponse.builder()
+ .productResponses(productResponses)
+ .pageCount(1)
+ .build();
+
SearchKeywordRequest searchKeywordRequest = SearchKeywordRequest.builder()
+ .address("")
+ .productName("")
+ .capacity(0L)
.category(Category.MOTEL.getName()).build();
Pageable pageable = Pageable.ofSize(10);
- doReturn(productResponses).when(productService).getProducts(searchKeywordRequest, pageable);
- ResponseEntity>> result = productRestController.getProducts(
+ doReturn(paginatedProductResponse).when(productService).getProducts(searchKeywordRequest, pageable);
+ ResponseEntity> result = productRestController.getProducts(
searchKeywordRequest.productName(), searchKeywordRequest.address(),
- searchKeywordRequest.category(), pageable);
+ searchKeywordRequest.category().get(0).getName(),searchKeywordRequest.capacity(), pageable);
//then
assertEquals(result.getStatusCode(), HttpStatus.OK);
- assertThat(result.getBody().getData()).usingRecursiveComparison()
+ assertThat(result.getBody().getData().productResponses()).usingRecursiveComparison()
.isEqualTo(productResponses);
}
@@ -58,7 +67,7 @@ void getAllProducts() {
void getProductDetails() {
//given
Product product = ProductFactory.createTestProduct();
- ProductDetailsResponse expectedResult = ProductMapper.toProductDetailsResponse(product);
+ ProductDetailsResponse expectedResult = ProductMapper.toProductDetailsResponse(product,false);
doReturn(expectedResult).when(productService)
.getProductDetails(1L, "2024-12-27", "2024-12-28");
//when
diff --git a/src/test/java/com/fc/shimpyo_be/domain/product/unit/repository/ProductRepositoryTest.java b/src/test/java/com/fc/shimpyo_be/domain/product/unit/repository/ProductRepositoryTest.java
new file mode 100644
index 00000000..79a750a1
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/domain/product/unit/repository/ProductRepositoryTest.java
@@ -0,0 +1,65 @@
+package com.fc.shimpyo_be.domain.product.unit.repository;
+
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.fc.shimpyo_be.config.TestJpaConfig;
+import com.fc.shimpyo_be.config.TestQuerydslConfig;
+import com.fc.shimpyo_be.domain.product.dto.request.SearchKeywordRequest;
+import com.fc.shimpyo_be.domain.product.entity.Product;
+import com.fc.shimpyo_be.domain.product.factory.ProductFactory;
+import com.fc.shimpyo_be.domain.product.repository.ProductCustomRepository;
+import com.fc.shimpyo_be.domain.product.repository.ProductCustomRepositoryImpl;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Import;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+
+@DataJpaTest
+@ComponentScan(basePackages = {"com.fc.shimpyo_be.domain.product.repository"})
+@Import({TestJpaConfig.class, TestQuerydslConfig.class})
+public class ProductRepositoryTest {
+
+ @Autowired
+ ProductCustomRepositoryImpl productCustomRepository;
+
+ @Autowired
+ ProductRepository productRepository;
+
+ @Autowired
+ RoomRepository roomRepository;
+
+
+ @Test
+ public void getProducts() {
+ //given
+ Product product = ProductFactory.createTestProduct();
+ Room room = ProductFactory.createTestRoom(product,0l);
+
+ productRepository.save(product);
+ roomRepository.save(room);
+
+ SearchKeywordRequest searchKeywordRequest = SearchKeywordRequest.builder()
+ .productName("")
+ .address("")
+ .category("")
+ .capacity(0L)
+ .build();
+
+ //when
+ Page products = productCustomRepository.findAllBySearchKeywordRequest(
+ searchKeywordRequest,
+ Pageable.ofSize(10));
+
+ //the
+ assertEquals(1, products.getContent().size());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/fc/shimpyo_be/domain/product/unit/service/ProductServiceTest.java b/src/test/java/com/fc/shimpyo_be/domain/product/unit/service/ProductServiceTest.java
index 187bcecf..ce5fd2fa 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/product/unit/service/ProductServiceTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/product/unit/service/ProductServiceTest.java
@@ -2,21 +2,27 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doReturn;
import com.fc.shimpyo_be.domain.product.dto.request.SearchKeywordRequest;
+import com.fc.shimpyo_be.domain.product.dto.response.PaginatedProductResponse;
import com.fc.shimpyo_be.domain.product.dto.response.ProductDetailsResponse;
-import com.fc.shimpyo_be.domain.product.dto.response.ProductResponse;
import com.fc.shimpyo_be.domain.product.entity.Category;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.factory.ProductFactory;
+import com.fc.shimpyo_be.domain.product.repository.ProductCustomRepositoryImpl;
import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
import com.fc.shimpyo_be.domain.product.service.ProductService;
import com.fc.shimpyo_be.domain.product.util.ProductMapper;
+import com.fc.shimpyo_be.domain.room.dto.response.RoomResponse;
import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.global.util.SecurityUtil;
import java.util.List;
import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -26,18 +32,27 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {
+ @Mock
+ private SecurityUtil securityUtil;
+
@Mock
private ProductRepository productRepository;
+ @Mock
+ private ProductCustomRepositoryImpl productCustomRepository;
+
@Spy
@InjectMocks
private ProductService productService;
+ @BeforeEach
+ void init() {
+ given(securityUtil.getNullableCurrentMemberId()).willReturn(null);
+ }
@Test
void getProducts() {
@@ -46,39 +61,44 @@ void getProducts() {
.productName("강릉 세인트 호텔").category(Category.MOTEL.getName()).address("강원도 강릉시 창해로 307")
.build();
- Specification spec = (root, query, criteriaBuilder) -> null;
-
List expectedProducts = ProductFactory.createTestProducts();
Pageable pageable = Pageable.ofSize(10);
Page productPage = new PageImpl<>(expectedProducts);
- given(productRepository.findAll(any(Specification.class), any(Pageable.class))).willReturn(
+ given(productCustomRepository.findAllBySearchKeywordRequest(any(SearchKeywordRequest.class),
+ any(Pageable.class))).willReturn(
productPage);
- //when
- List result = productService.getProducts(searchKeywordRequest, pageable);
+ PaginatedProductResponse result = productService.getProducts(searchKeywordRequest,
+ pageable);
//then
- assertThat(result).usingRecursiveAssertion().isEqualTo(
- productPage.getContent().stream().map(ProductMapper::toProductResponse).toList());
-
+ assertThat(result.productResponses()).usingRecursiveAssertion().isEqualTo(
+ productPage.getContent().stream()
+ .map(product -> ProductMapper.toProductResponse(product, false)).toList());
}
@Test
void getProductDetails() {
//given
Product product = ProductFactory.createTestProduct();
- Room room = ProductFactory.createTestRoom(product);
+ Room room = ProductFactory.createTestRoom(product, 0L);
product.getRooms().add(room);
given(productRepository.findById(product.getId())).willReturn(Optional.ofNullable(product));
- doReturn(true).when(
- productService).isAvailableForReservation(product.getId(), "2023-11-27", "2023-11-28");
+ doReturn(1L).when(
+ productService)
+ .countAvailableForReservationUsingRoomCode(anyLong(), anyString(), anyString());
+
//when
ProductDetailsResponse result = productService.getProductDetails(product.getId(),
"2023-11-27", "2023-11-28");
//then
for (int i = 0; i < result.rooms().size(); i++) {
+ RoomResponse roomResponse = ProductMapper.toProductDetailsResponse(product, false)
+ .rooms()
+ .get(i);
+ roomResponse.setRemaining(1L);
assertThat(result.rooms().get(i)).usingRecursiveComparison()
- .isEqualTo(ProductMapper.toProductDetailsResponse(product).rooms().get(i));
+ .isEqualTo(roomResponse);
}
}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservation/docs/ReservationRestControllerDocsTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservation/docs/ReservationRestControllerDocsTest.java
index 35cdd980..1dee7527 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservation/docs/ReservationRestControllerDocsTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservation/docs/ReservationRestControllerDocsTest.java
@@ -4,12 +4,14 @@
import com.fc.shimpyo_be.domain.reservation.dto.request.*;
import com.fc.shimpyo_be.domain.reservation.dto.response.ReservationInfoResponseDto;
import com.fc.shimpyo_be.domain.reservation.dto.response.SaveReservationResponseDto;
-import com.fc.shimpyo_be.domain.reservation.dto.response.ValidationResultResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyResultResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyRoomResponseDto;
import com.fc.shimpyo_be.domain.reservation.entity.PayMethod;
import com.fc.shimpyo_be.domain.reservation.facade.PreoccupyRoomsLockFacade;
import com.fc.shimpyo_be.domain.reservation.facade.ReservationLockFacade;
import com.fc.shimpyo_be.domain.reservation.service.ReservationService;
import com.fc.shimpyo_be.domain.reservationproduct.dto.request.ReservationProductRequestDto;
+import com.fc.shimpyo_be.domain.reservationproduct.dto.response.ReservationProductResponseDto;
import com.fc.shimpyo_be.global.util.SecurityUtil;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -22,7 +24,6 @@
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.security.test.context.support.WithMockUser;
-import java.util.ArrayList;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
@@ -79,24 +80,72 @@ void saveReservation() throws Exception {
String requestUrl = "/api/reservations";
SaveReservationRequestDto requestDto
- = new SaveReservationRequestDto(
- List.of(
- new ReservationProductRequestDto(
- 1L, "신라호텔", "디럭스 더블1",
- 2, 4, "2023-11-20", "2023-11-23",
- "13:00", "12:00",
- "홍길동", "010-1111-1111", 300000
- ),
- new ReservationProductRequestDto(
- 3L, "강릉 고즈넉한 펜션", "숲의 방",
- 6, 9, "2023-12-10", "2023-12-12",
- "13:00", "12:00",
- "김갑돌", "010-2222-2222", 150000
+ = SaveReservationRequestDto.builder()
+ .reservationProducts(
+ List.of(
+ ReservationProductRequestDto.builder()
+ .cartId(2L)
+ .roomId(1L)
+ .startDate("2023-11-20")
+ .endDate("2023-11-23")
+ .visitorName("visitor1")
+ .visitorPhone("010-1111-1111")
+ .price(300000)
+ .build(),
+ ReservationProductRequestDto.builder()
+ .cartId(7L)
+ .roomId(2L)
+ .startDate("2023-12-10")
+ .endDate("2023-12-12")
+ .visitorName("visitor2")
+ .visitorPhone("010-2222-2222")
+ .price(150000)
+ .build()
)
- ), PayMethod.CREDIT_CARD, 450000
- );
-
- SaveReservationResponseDto responseDto = new SaveReservationResponseDto(1L, requestDto);
+ )
+ .payMethod(PayMethod.CREDIT_CARD)
+ .totalPrice(450000)
+ .build();
+
+ SaveReservationResponseDto responseDto
+ = SaveReservationResponseDto.builder()
+ .reservationId(1L)
+ .reservationProducts(
+ List.of(
+ ReservationProductResponseDto.builder()
+ .productName("숙소1")
+ .roomId(1L)
+ .roomName("객실1")
+ .standard(2)
+ .capacity(3)
+ .startDate("2023-11-20")
+ .endDate("2023-11-23")
+ .checkIn("13:00")
+ .checkOut("12:00")
+ .visitorName("visitor1")
+ .visitorPhone("010-1111-1111")
+ .price(300000)
+ .build(),
+ ReservationProductResponseDto.builder()
+ .productName("숙소2")
+ .roomId(2L)
+ .roomName("객실2")
+ .standard(2)
+ .capacity(3)
+ .startDate("2023-12-10")
+ .endDate("2023-12-1")
+ .checkIn("13:00")
+ .checkOut("12:00")
+ .visitorName("visitor2")
+ .visitorPhone("010-2222-2222")
+ .price(150000)
+ .build()
+ )
+ )
+ .payMethod(requestDto.payMethod())
+ .totalPrice(requestDto.totalPrice())
+ .createdAt("2023-12-06 10:30:35")
+ .build();
given(securityUtil.getCurrentMemberId()).willReturn(1L);
given(reservationLockFacade.saveReservation(anyLong(), any(SaveReservationRequestDto.class)))
@@ -109,36 +158,21 @@ void saveReservation() throws Exception {
.andExpect(status().isCreated())
.andDo(restDoc.document(
requestFields(
- fieldWithPath("reservationProducts").type(JsonFieldType.ARRAY).description("예약할 객실 상품 리스트")
+ fieldWithPath("reservationProducts").type(JsonFieldType.ARRAY).description("예약할 객실 숙소 리스트")
.attributes(key("constraints").value(
saveReservationDescriptions.descriptionsForProperty("reservationProducts"))),
+ fieldWithPath("reservationProducts[].cartId").type(JsonFieldType.NUMBER).description("장바구니 식별자")
+ .attributes(key("constraints").value(
+ reservationProductDescriptions.descriptionsForProperty("cartId"))),
fieldWithPath("reservationProducts[].roomId").type(JsonFieldType.NUMBER).description("예약할 객실 식별자")
.attributes(key("constraints").value(
reservationProductDescriptions.descriptionsForProperty("roomId"))),
- fieldWithPath("reservationProducts[].productName").type(JsonFieldType.STRING).description("예약할 숙소명")
- .attributes(key("constraints").value(
- reservationProductDescriptions.descriptionsForProperty("productName"))),
- fieldWithPath("reservationProducts[].roomName").type(JsonFieldType.STRING).description("예약할 객실명")
- .attributes(key("constraints").value(
- reservationProductDescriptions.descriptionsForProperty("roomName"))),
- fieldWithPath("reservationProducts[].standard").type(JsonFieldType.NUMBER).description("객실 기준 인원")
- .attributes(key("constraints").value(
- reservationProductDescriptions.descriptionsForProperty("standard"))),
- fieldWithPath("reservationProducts[].max").type(JsonFieldType.NUMBER).description("객실 최대 인원")
- .attributes(key("constraints").value(
- reservationProductDescriptions.descriptionsForProperty("max"))),
fieldWithPath("reservationProducts[].startDate").type(JsonFieldType.STRING).description("숙박 시작일")
.attributes(key("constraints").value(
reservationProductDescriptions.descriptionsForProperty("startDate"))),
fieldWithPath("reservationProducts[].endDate").type(JsonFieldType.STRING).description("숙박 마지막일")
.attributes(key("constraints").value(
reservationProductDescriptions.descriptionsForProperty("endDate"))),
- fieldWithPath("reservationProducts[].checkIn").type(JsonFieldType.STRING).description("체크인 시간")
- .attributes(key("constraints").value(
- reservationProductDescriptions.descriptionsForProperty("checkIn"))),
- fieldWithPath("reservationProducts[].checkOut").type(JsonFieldType.STRING).description("체크아웃 시간")
- .attributes(key("constraints").value(
- reservationProductDescriptions.descriptionsForProperty("checkOut"))),
fieldWithPath("reservationProducts[].visitorName").type(JsonFieldType.STRING).description("방문자명")
.attributes(key("constraints").value(
reservationProductDescriptions.descriptionsForProperty("visitorName"))),
@@ -159,12 +193,12 @@ void saveReservation() throws Exception {
responseFields(responseCommon()).and(
fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
fieldWithPath("data.reservationId").type(JsonFieldType.NUMBER).description("예약 식별자"),
- fieldWithPath("data.reservationProducts").type(JsonFieldType.ARRAY).description("예약 상품 리스트"),
- fieldWithPath("data.reservationProducts[].roomId").type(JsonFieldType.NUMBER).description("객실 식별자"),
+ fieldWithPath("data.reservationProducts").type(JsonFieldType.ARRAY).description("예약 숙소 리스트"),
fieldWithPath("data.reservationProducts[].productName").type(JsonFieldType.STRING).description("숙소명"),
+ fieldWithPath("data.reservationProducts[].roomId").type(JsonFieldType.NUMBER).description("객실 식별자"),
fieldWithPath("data.reservationProducts[].roomName").type(JsonFieldType.STRING).description("객실명"),
fieldWithPath("data.reservationProducts[].standard").type(JsonFieldType.NUMBER).description("기준 인원"),
- fieldWithPath("data.reservationProducts[].max").type(JsonFieldType.NUMBER).description("최대 인원"),
+ fieldWithPath("data.reservationProducts[].capacity").type(JsonFieldType.NUMBER).description("최대 인원"),
fieldWithPath("data.reservationProducts[].startDate").type(JsonFieldType.STRING).description("숙박 시작일"),
fieldWithPath("data.reservationProducts[].endDate").type(JsonFieldType.STRING).description("숙박 마지막일"),
fieldWithPath("data.reservationProducts[].checkIn").type(JsonFieldType.STRING).description("체크인 시간"),
@@ -173,8 +207,9 @@ void saveReservation() throws Exception {
fieldWithPath("data.reservationProducts[].visitorPhone").type(JsonFieldType.STRING).description("방문자 전화번호"),
fieldWithPath("data.reservationProducts[].price").type(JsonFieldType.NUMBER).description("객실 이용 가격"),
fieldWithPath("data.payMethod").type(JsonFieldType.STRING).description("결제 수단"),
- fieldWithPath("data.totalPrice").type(JsonFieldType.NUMBER).description("총 결제 금액")
- )
+ fieldWithPath("data.totalPrice").type(JsonFieldType.NUMBER).description("총 결제 금액"),
+ fieldWithPath("data.createdAt").type(JsonFieldType.STRING).description("예약 주문 시간")
+ )
)
);
@@ -191,25 +226,27 @@ void getReservationInfoList() throws Exception {
int page = 0;
PageRequest pageRequest = PageRequest.of(page, size);
- List content
- = List.of(
- new ReservationInfoResponseDto(
- 2L,
- 3L,
- 5L,
- "호텔1",
- "호텔1 photoUrl",
- "호텔1 주소 url",
- 1L,
- "객실1",
- "2023-11-23",
- "2023-11-26",
- "14:00",
- "12:00",
- 220000,
- "CREDIT_CARD"
- )
- );
+ List content =
+ List.of(
+ ReservationInfoResponseDto.builder()
+ .reservationId(2L)
+ .reservationProductId(3L)
+ .productId(5L)
+ .productName("호텔1")
+ .productImageUrl("호텔1 photo URL")
+ .productAddress("호텔1 주소")
+ .productDetailAddress("호텔 상세 주소")
+ .roomId(1L)
+ .roomName("객실1")
+ .startDate("2023-11-23")
+ .endDate("2023-11-26")
+ .checkIn("14:00")
+ .checkOut("12:00")
+ .price(220000)
+ .payMethod("CREDIT_CARD")
+ .createdAt("2023-11-20 10:00:00")
+ .build()
+ );
given(securityUtil.getCurrentMemberId()).willReturn(1L);
given(reservationService.getReservationInfoList(anyLong(), any(Pageable.class)))
@@ -231,20 +268,22 @@ void getReservationInfoList() throws Exception {
responseFields(responseCommon()).and(
fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
fieldWithPath("data.content").type(JsonFieldType.ARRAY).description("조회 데이터 리스트"),
- fieldWithPath("data.content.[].reservationId").type(JsonFieldType.NUMBER).description("예약 식별자"),
- fieldWithPath("data.content.[].reservationProductId").type(JsonFieldType.NUMBER).description("예약 상품 식별자"),
- fieldWithPath("data.content.[].productId").type(JsonFieldType.NUMBER).description("상품 식별자"),
- fieldWithPath("data.content.[].productName").type(JsonFieldType.STRING).description("숙소명"),
- fieldWithPath("data.content.[].productImageUrl").type(JsonFieldType.STRING).description("숙소 대표 이미지 URL"),
- fieldWithPath("data.content.[].productAddress").type(JsonFieldType.STRING).description("숙소 주소"),
- fieldWithPath("data.content.[].roomId").type(JsonFieldType.NUMBER).description("예약한 객실 식별자"),
- fieldWithPath("data.content.[].roomName").type(JsonFieldType.STRING).description("객실명"),
- fieldWithPath("data.content.[].startDate").type(JsonFieldType.STRING).description("숙박 시작일"),
- fieldWithPath("data.content.[].endDate").type(JsonFieldType.STRING).description("숙박 마지막일"),
- fieldWithPath("data.content.[].checkIn").type(JsonFieldType.STRING).description("체크인 시간"),
- fieldWithPath("data.content.[].checkOut").type(JsonFieldType.STRING).description("체크아웃 시간"),
- fieldWithPath("data.content.[].price").type(JsonFieldType.NUMBER).description("결제 금액"),
- fieldWithPath("data.content.[].payMethod").type(JsonFieldType.STRING).description("결제 수단"),
+ fieldWithPath("data.content[].reservationId").type(JsonFieldType.NUMBER).description("예약 식별자"),
+ fieldWithPath("data.content[].reservationProductId").type(JsonFieldType.NUMBER).description("예약 숙소 식별자"),
+ fieldWithPath("data.content[].productId").type(JsonFieldType.NUMBER).description("숙소 식별자"),
+ fieldWithPath("data.content[].productName").type(JsonFieldType.STRING).description("숙소명"),
+ fieldWithPath("data.content[].productImageUrl").type(JsonFieldType.STRING).description("숙소 대표 이미지 URL"),
+ fieldWithPath("data.content[].productAddress").type(JsonFieldType.STRING).description("숙소 주소"),
+ fieldWithPath("data.content[].productDetailAddress").type(JsonFieldType.STRING).description("숙소 상세 주소"),
+ fieldWithPath("data.content[].roomId").type(JsonFieldType.NUMBER).description("예약한 객실 식별자"),
+ fieldWithPath("data.content[].roomName").type(JsonFieldType.STRING).description("객실명"),
+ fieldWithPath("data.content[].startDate").type(JsonFieldType.STRING).description("숙박 시작일"),
+ fieldWithPath("data.content[].endDate").type(JsonFieldType.STRING).description("숙박 마지막일"),
+ fieldWithPath("data.content[].checkIn").type(JsonFieldType.STRING).description("체크인 시간"),
+ fieldWithPath("data.content[].checkOut").type(JsonFieldType.STRING).description("체크아웃 시간"),
+ fieldWithPath("data.content[].price").type(JsonFieldType.NUMBER).description("결제 금액"),
+ fieldWithPath("data.content[].payMethod").type(JsonFieldType.STRING).description("결제 수단"),
+ fieldWithPath("data.content[].createdAt").type(JsonFieldType.STRING).description("예약 결제 완료 일시"),
fieldWithPath("data.pageable.sort.sorted").type(JsonFieldType.BOOLEAN).description("정렬 여부"),
fieldWithPath("data.pageable.sort.empty").type(JsonFieldType.BOOLEAN).description("데이터가 비었는지 여부"),
@@ -280,22 +319,47 @@ void checkAvailableAndPreoccupy() throws Exception {
// given
String requestUrl = "/api/reservations/preoccupy";
- PreoccupyRoomsRequestDto requestDto
- = new PreoccupyRoomsRequestDto(
- List.of(
- new PreoccupyRoomItemRequestDto(1L, "2023-12-23", "2023-12-25"),
- new PreoccupyRoomItemRequestDto(2L, "2023-11-11", "2023-11-14")
- )
- );
-
- ValidationResultResponseDto responseDto
- = new ValidationResultResponseDto(true, new ArrayList<>());
+ PreoccupyRoomsRequestDto requestDto =
+ PreoccupyRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(1L).roomCode(1001L).startDate("2023-12-23").endDate("2023-12-25")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(2L).roomCode(1002L).startDate("2023-11-11").endDate("2023-11-14")
+ .build()
+ )
+ )
+ .build();
+
+ ValidatePreoccupyResultResponseDto responseDto =
+ ValidatePreoccupyResultResponseDto.builder()
+ .isAvailable(true)
+ .roomResults(
+ List.of(
+ ValidatePreoccupyRoomResponseDto.builder()
+ .cartId(1L)
+ .roomCode(1001L)
+ .startDate("2023-12-23")
+ .endDate("2023-12-25")
+ .roomId(1L)
+ .build(),
+ ValidatePreoccupyRoomResponseDto.builder()
+ .cartId(2L)
+ .roomCode(1002L)
+ .startDate("2023-11-11")
+ .endDate("2023-11-14")
+ .roomId(3L)
+ .build()
+ )
+ )
+ .build();
given(securityUtil.getCurrentMemberId())
.willReturn(1L);
- willDoNothing()
- .given(preoccupyRoomsLockFacade)
- .checkAvailableAndPreoccupy(1L, requestDto);
+ given(preoccupyRoomsLockFacade.checkAvailableAndPreoccupy(1L, requestDto))
+ .willReturn(responseDto);
// when
mockMvc.perform(post(requestUrl)
@@ -307,9 +371,12 @@ void checkAvailableAndPreoccupy() throws Exception {
fieldWithPath("rooms").type(JsonFieldType.ARRAY).description("예약할 객실 리스트")
.attributes(key("constraints").value(
preoccupyRoomsDescriptions.descriptionsForProperty("rooms"))),
- fieldWithPath("rooms[].roomId").type(JsonFieldType.NUMBER).description("예약할 객실 식별자")
+ fieldWithPath("rooms[].cartId").type(JsonFieldType.NUMBER).description("장바구니 식별자")
+ .attributes(key("constraints").value(
+ preoccupyRoomItemDescriptions.descriptionsForProperty("cartId"))),
+ fieldWithPath("rooms[].roomCode").type(JsonFieldType.NUMBER).description("예약할 객실 코드")
.attributes(key("constraints").value(
- preoccupyRoomItemDescriptions.descriptionsForProperty("roomId"))),
+ preoccupyRoomItemDescriptions.descriptionsForProperty("roomCode"))),
fieldWithPath("rooms[].startDate").type(JsonFieldType.STRING).description("숙박 시작일")
.attributes(key("constraints").value(
preoccupyRoomItemDescriptions.descriptionsForProperty("startDate"))),
@@ -318,7 +385,14 @@ void checkAvailableAndPreoccupy() throws Exception {
preoccupyRoomItemDescriptions.descriptionsForProperty("endDate")))
),
responseFields(responseCommon()).and(
- fieldWithPath("data").type(JsonFieldType.NULL).description("응답 데이터")
+ fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
+ fieldWithPath("data.isAvailable").type(JsonFieldType.BOOLEAN).description("예약 선점 가능 여부"),
+ fieldWithPath("data.roomResults").type(JsonFieldType.ARRAY).description("객실별 예약 선점 가능 검증 결과 리스트"),
+ fieldWithPath("data.roomResults[].cartId").type(JsonFieldType.NUMBER).description("장바구니 식별자"),
+ fieldWithPath("data.roomResults[].roomCode").type(JsonFieldType.NUMBER).description("객실 코드"),
+ fieldWithPath("data.roomResults[].startDate").type(JsonFieldType.STRING).description("숙박 시작일"),
+ fieldWithPath("data.roomResults[].endDate").type(JsonFieldType.STRING).description("숙박 마지막일"),
+ fieldWithPath("data.roomResults[].roomId").type(JsonFieldType.NUMBER).description("매칭된 객실 식별자 (-1인 경우, 예약 선점 불가능한 객실)")
)
)
);
@@ -333,13 +407,22 @@ void releaseRooms() throws Exception {
// given
String requestUrl = "/api/reservations/release";
- ReleaseRoomsRequestDto requestDto = new ReleaseRoomsRequestDto(
- List.of(
- new ReleaseRoomItemRequestDto(1L, "2023-12-23", "2023-12-25"),
- new ReleaseRoomItemRequestDto(2L, "2023-11-11", "2023-11-14"),
- new ReleaseRoomItemRequestDto(3L, "2023-11-15", "2023-11-16")
- )
- );
+ ReleaseRoomsRequestDto requestDto =
+ ReleaseRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ ReleaseRoomItemRequestDto.builder()
+ .roomId(1L).startDate("2023-12-23").endDate("2023-12-25")
+ .build(),
+ ReleaseRoomItemRequestDto.builder()
+ .roomId(2L).startDate("2023-11-11").endDate("2023-11-14")
+ .build(),
+ ReleaseRoomItemRequestDto.builder()
+ .roomId(3L).startDate("2023-11-15").endDate("2023-11-16")
+ .build()
+ )
+ )
+ .build();
given(securityUtil.getCurrentMemberId())
.willReturn(1L);
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/controller/ReservationRestControllerTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/controller/ReservationRestControllerTest.java
index cf317db2..7fd17cf2 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/controller/ReservationRestControllerTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/controller/ReservationRestControllerTest.java
@@ -5,12 +5,14 @@
import com.fc.shimpyo_be.domain.reservation.dto.request.*;
import com.fc.shimpyo_be.domain.reservation.dto.response.ReservationInfoResponseDto;
import com.fc.shimpyo_be.domain.reservation.dto.response.SaveReservationResponseDto;
-import com.fc.shimpyo_be.domain.reservation.dto.response.ValidationResultResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyResultResponseDto;
+import com.fc.shimpyo_be.domain.reservation.dto.response.ValidatePreoccupyRoomResponseDto;
import com.fc.shimpyo_be.domain.reservation.entity.PayMethod;
import com.fc.shimpyo_be.domain.reservation.facade.PreoccupyRoomsLockFacade;
import com.fc.shimpyo_be.domain.reservation.facade.ReservationLockFacade;
import com.fc.shimpyo_be.domain.reservation.service.ReservationService;
import com.fc.shimpyo_be.domain.reservationproduct.dto.request.ReservationProductRequestDto;
+import com.fc.shimpyo_be.domain.reservationproduct.dto.response.ReservationProductResponseDto;
import com.fc.shimpyo_be.global.util.SecurityUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -27,10 +29,9 @@
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
-import java.util.ArrayList;
import java.util.List;
-import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
@@ -78,18 +79,72 @@ void saveReservation_Api_test() throws Exception {
String requestUrl = "/api/reservations";
SaveReservationRequestDto requestDto
- = new SaveReservationRequestDto(
- List.of(
- getReservationProductRequestData(
- 1L, "2023-11-20", "2023-11-23",
- "visitor1", "010-1111-1111", 150000),
- getReservationProductRequestData(
- 2L, "2023-11-18", "2023-11-20",
- "visitor2", "010-2222-2222", 200000)
- ), PayMethod.CREDIT_CARD, 350000
- );
+ = SaveReservationRequestDto.builder()
+ .reservationProducts(
+ List.of(
+ ReservationProductRequestDto.builder()
+ .cartId(1L)
+ .roomId(1L)
+ .startDate("2023-11-20")
+ .endDate("2023-11-23")
+ .visitorName("visitor1")
+ .visitorPhone("010-1111-1111")
+ .price(300000)
+ .build(),
+ ReservationProductRequestDto.builder()
+ .cartId(2L)
+ .roomId(3L)
+ .startDate("2023-12-10")
+ .endDate("2023-12-12")
+ .visitorName("visitor2")
+ .visitorPhone("010-2222-2222")
+ .price(150000)
+ .build()
+ )
+ )
+ .payMethod(PayMethod.CREDIT_CARD)
+ .totalPrice(450000)
+ .build();
- SaveReservationResponseDto responseDto = new SaveReservationResponseDto(1L, requestDto);
+ SaveReservationResponseDto responseDto =
+ SaveReservationResponseDto.builder()
+ .reservationId(1L)
+ .reservationProducts(
+ List.of(
+ ReservationProductResponseDto.builder()
+ .productName("숙소1")
+ .roomId(1L)
+ .roomName("객실1")
+ .standard(2)
+ .capacity(3)
+ .startDate("2023-11-20")
+ .endDate("2023-11-23")
+ .checkIn("13:00")
+ .checkOut("12:00")
+ .visitorName("visitor1")
+ .visitorPhone("010-1111-1111")
+ .price(300000)
+ .build(),
+ ReservationProductResponseDto.builder()
+ .productName("숙소2")
+ .roomId(3L)
+ .roomName("객실3")
+ .standard(2)
+ .capacity(3)
+ .startDate("2023-12-10")
+ .endDate("2023-12-12")
+ .checkIn("13:00")
+ .checkOut("12:00")
+ .visitorName("visitor2")
+ .visitorPhone("010-2222-2222")
+ .price(150000)
+ .build()
+ )
+ )
+ .payMethod(requestDto.payMethod())
+ .totalPrice(requestDto.totalPrice())
+ .createdAt("2023-12-06 10:30:35")
+ .build();
given(securityUtil.getCurrentMemberId()).willReturn(1L);
given(reservationLockFacade.saveReservation(anyLong(), any(SaveReservationRequestDto.class)))
@@ -118,22 +173,24 @@ void getReservationInfoList_Api_test() throws Exception {
PageRequest pageRequest = PageRequest.of(page, size);
List content
= List.of(
- new ReservationInfoResponseDto(
- 2L,
- 3L,
- 5L,
- "호텔2",
- "호텔 photoUrl",
- "호텔 주소 url",
- 1L,
- "객실1",
- "2023-11-23",
- "2023-11-26",
- "14:00",
- "12:00",
- 220000,
- "CREDIT_CARD"
- )
+ ReservationInfoResponseDto.builder()
+ .reservationId(2L)
+ .reservationProductId(3L)
+ .productId(5L)
+ .productName("호텔1")
+ .productImageUrl("호텔1 photo URL")
+ .productAddress("호텔1 주소")
+ .productDetailAddress("호텔 상세 주소")
+ .roomId(1L)
+ .roomName("객실1")
+ .startDate("2023-11-23")
+ .endDate("2023-11-26")
+ .checkIn("14:00")
+ .checkOut("12:00")
+ .price(220000)
+ .payMethod("CREDIT_CARD")
+ .createdAt("2023-11-20 10:00:00")
+ .build()
);
given(securityUtil.getCurrentMemberId()).willReturn(1L);
@@ -158,31 +215,62 @@ void checkAvailableAndPreoccupy_test() throws Exception {
// given
String requestUrl = "/api/reservations/preoccupy";
- PreoccupyRoomsRequestDto requestDto
- = new PreoccupyRoomsRequestDto(
- List.of(
- new PreoccupyRoomItemRequestDto(1L, "2023-12-23", "2023-12-25"),
- new PreoccupyRoomItemRequestDto(2L, "2023-11-11", "2023-11-14")
+ PreoccupyRoomsRequestDto requestDto =
+ PreoccupyRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(1L)
+ .roomCode(1001L)
+ .startDate("2023-12-23")
+ .endDate("2023-12-25")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(2L)
+ .roomCode(1003L)
+ .startDate("2023-11-11")
+ .endDate("2023-11-14")
+ .build()
+ )
)
- );
-
- ValidationResultResponseDto responseDto
- = new ValidationResultResponseDto(true, new ArrayList<>());
+ .build();
+
+ ValidatePreoccupyResultResponseDto responseDto =
+ ValidatePreoccupyResultResponseDto.builder()
+ .isAvailable(true)
+ .roomResults(
+ List.of(
+ ValidatePreoccupyRoomResponseDto.builder()
+ .cartId(1L)
+ .roomCode(1001L)
+ .startDate("2023-12-23")
+ .endDate("2023-12-25")
+ .roomId(1L)
+ .build(),
+ ValidatePreoccupyRoomResponseDto.builder()
+ .cartId(2L)
+ .roomCode(1003L)
+ .startDate("2023-11-11")
+ .endDate("2023-11-14")
+ .roomId(3L)
+ .build()
+ )
+ )
+ .build();
given(securityUtil.getCurrentMemberId())
.willReturn(1L);
- willDoNothing()
- .given(preoccupyRoomsLockFacade)
- .checkAvailableAndPreoccupy(1L, requestDto);
+ given(preoccupyRoomsLockFacade.checkAvailableAndPreoccupy(1L, requestDto))
+ .willReturn(responseDto);
// when & then
mockMvc.perform(
- post(requestUrl)
- .content(objectMapper.writeValueAsString(requestDto))
- .contentType(MediaType.APPLICATION_JSON)
- )
+ post(requestUrl)
+ .content(objectMapper.writeValueAsString(requestDto))
+ .contentType(MediaType.APPLICATION_JSON)
+ )
.andExpect(status().isOk())
- .andExpect(jsonPath("$.data").isEmpty());
+ .andExpect(jsonPath("$.data.isAvailable", is(true)));
}
@WithMockUser(roles = "USER")
@@ -192,24 +280,28 @@ void checkAvailableAndPreoccupy_size_validation_error_test() throws Exception {
// given
String requestUrl = "/api/reservations/preoccupy";
- PreoccupyRoomsRequestDto requestDto
- = new PreoccupyRoomsRequestDto(
- List.of(
- new PreoccupyRoomItemRequestDto(1L, "2023-12-23", "2023-12-25"),
- new PreoccupyRoomItemRequestDto(2L, "2023-11-11", "2023-11-14"),
- new PreoccupyRoomItemRequestDto(3L, "2023-11-11", "2023-11-14"),
- new PreoccupyRoomItemRequestDto(4L, "2023-11-11", "2023-11-14")
- )
- );
-
- ValidationResultResponseDto responseDto
- = new ValidationResultResponseDto(true, new ArrayList<>());
+ PreoccupyRoomsRequestDto requestDto =
+ PreoccupyRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(1L).roomCode(1001L).startDate("2023-12-23").endDate("2023-12-25")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(2L).roomCode(1002L).startDate("2023-11-11").endDate("2023-11-14")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(3L).roomCode(1003L).startDate("2023-11-11").endDate("2023-11-14")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(4L).roomCode(1004L).startDate("2023-11-16").endDate("2023-11-18")
+ .build()
+ )
+ )
+ .build();
given(securityUtil.getCurrentMemberId())
.willReturn(1L);
- willDoNothing()
- .given(preoccupyRoomsLockFacade)
- .checkAvailableAndPreoccupy(1L, requestDto);
// when & then
mockMvc.perform(
@@ -228,23 +320,25 @@ void checkAvailableAndPreoccupy_localdate_validation_error_test() throws Excepti
// given
String requestUrl = "/api/reservations/preoccupy";
- PreoccupyRoomsRequestDto requestDto
- = new PreoccupyRoomsRequestDto(
- List.of(
- new PreoccupyRoomItemRequestDto(1L, "202-12-23", "2023-12-25"),
- new PreoccupyRoomItemRequestDto(2L, "2023-11-11", "2023-11-14"),
- new PreoccupyRoomItemRequestDto(3L, "2023-11-11", "2023-11-14")
- )
- );
-
- ValidationResultResponseDto responseDto
- = new ValidationResultResponseDto(true, new ArrayList<>());
+ PreoccupyRoomsRequestDto requestDto =
+ PreoccupyRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(1L).roomCode(1001L).startDate("202-12-23").endDate("2023-12-25")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(2L).roomCode(1002L).startDate("2023-11-11").endDate("2023-11-14")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .cartId(3L).roomCode(1003L).startDate("2023-11-11").endDate("2023-11-14")
+ .build()
+ )
+ )
+ .build();
given(securityUtil.getCurrentMemberId())
.willReturn(1L);
- willDoNothing()
- .given(preoccupyRoomsLockFacade)
- .checkAvailableAndPreoccupy(1L, requestDto);
// when & then
mockMvc.perform(
@@ -263,13 +357,22 @@ void releaseRooms_test() throws Exception {
// given
String requestUrl = "/api/reservations/release";
- ReleaseRoomsRequestDto requestDto = new ReleaseRoomsRequestDto(
- List.of(
- new ReleaseRoomItemRequestDto(1L, "2023-12-23", "2023-12-25"),
- new ReleaseRoomItemRequestDto(2L, "2023-11-11", "2023-11-14"),
- new ReleaseRoomItemRequestDto(3L, "2023-11-15", "2023-11-16")
- )
- );
+ ReleaseRoomsRequestDto requestDto =
+ ReleaseRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ ReleaseRoomItemRequestDto.builder()
+ .roomId(1L).startDate("2023-12-23").endDate("2023-12-25")
+ .build(),
+ ReleaseRoomItemRequestDto.builder()
+ .roomId(2L).startDate("2023-11-11").endDate("2023-11-14")
+ .build(),
+ ReleaseRoomItemRequestDto.builder()
+ .roomId(3L).startDate("2023-11-15").endDate("2023-11-16")
+ .build()
+ )
+ )
+ .build();
given(securityUtil.getCurrentMemberId())
.willReturn(1L);
@@ -287,29 +390,4 @@ void releaseRooms_test() throws Exception {
.andExpect(jsonPath("$.code", is(200)))
.andExpect(jsonPath("$.data").isEmpty());
}
-
- private ReservationProductRequestDto getReservationProductRequestData(
- long roomId,
- String startDate,
- String endDate,
- String visitorName,
- String visitorPhone,
- Integer price
- ) {
- String defaultValue = "DEFAULT_VALUE";
- return new ReservationProductRequestDto(
- roomId,
- defaultValue,
- defaultValue,
- 2,
- 4,
- startDate,
- endDate,
- "13:00",
- "12:00",
- visitorName,
- visitorPhone,
- price
- );
- }
}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/facade/PreoccupyRoomsLockFacadeTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/facade/PreoccupyRoomsLockFacadeTest.java
index 4a3b6df2..64c25aac 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/facade/PreoccupyRoomsLockFacadeTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/facade/PreoccupyRoomsLockFacadeTest.java
@@ -1,29 +1,191 @@
package com.fc.shimpyo_be.domain.reservation.unit.facade;
import com.fc.shimpyo_be.config.AbstractContainersSupport;
+import com.fc.shimpyo_be.config.DatabaseCleanUp;
+import com.fc.shimpyo_be.config.TestDBCleanerConfig;
+import com.fc.shimpyo_be.domain.product.entity.*;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
import com.fc.shimpyo_be.domain.reservation.dto.request.PreoccupyRoomItemRequestDto;
import com.fc.shimpyo_be.domain.reservation.dto.request.PreoccupyRoomsRequestDto;
import com.fc.shimpyo_be.domain.reservation.facade.PreoccupyRoomsLockFacade;
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
+import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
import org.springframework.util.StopWatch;
+import java.time.LocalTime;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadLocalRandom;
@Slf4j
+@Import(TestDBCleanerConfig.class)
@SpringBootTest
public class PreoccupyRoomsLockFacadeTest extends AbstractContainersSupport {
@Autowired
private PreoccupyRoomsLockFacade preoccupyRoomsLockFacade;
+ @Autowired
+ private RoomRepository roomRepository;
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ @Autowired
+ private DatabaseCleanUp databaseCleanUp;
+
+ private final String[] tableNameArray
+ = {"product", "product_option", "address", "room", "room_option", "amenity", "room_price"};
+
private static final ThreadLocal threadLocalStopWatch = ThreadLocal.withInitial(StopWatch::new);
+ @BeforeEach
+ void setUp() {
+ databaseCleanUp.cleanUp(tableNameArray);
+
+ List products = new ArrayList<>();
+ for (int i = 1; i <= 3; i++) {
+ String productName = "호텔" + i;
+ float starAvg = ThreadLocalRandom.current().nextFloat(0, 5);
+ String infoCenter = String.format("02-1234-%d%d%d%d", i, i, i, i);
+ products.add(
+ productRepository.save(
+ Product.builder()
+ .name(productName)
+ .thumbnail(productName + " 썸네일 url")
+ .description(productName + " 설명")
+ .starAvg(starAvg)
+ .category(Category.TOURIST_HOTEL)
+ .address(
+ Address.builder()
+ .address(productName + " 주소")
+ .detailAddress(productName + " 상세 주소")
+ .mapX(1.0)
+ .mapY(1.5)
+ .build()
+ )
+ .productOption(
+ ProductOption.builder()
+ .cooking(true)
+ .foodPlace("음료 가능")
+ .parking(true)
+ .pickup(false)
+ .infoCenter(infoCenter)
+ .build()
+ )
+ .amenity(
+ Amenity.builder()
+ .barbecue(false)
+ .beauty(true)
+ .beverage(true)
+ .fitness(true)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(true)
+ .publicBath(true)
+ .publicPc(true)
+ .seminar(false)
+ .sports(false)
+ .build()
+ )
+ .build()
+ )
+ );
+ }
+
+ for (int i = 1; i <= 3; i++) {
+ String roomName = "객실" + i;
+ roomRepository.save(
+ Room.builder()
+ .product(products.get(i - 1))
+ .name(roomName)
+ .code(1000 + i)
+ .description(roomName + " 설명")
+ .standard(2)
+ .capacity(4)
+ .checkIn(LocalTime.of(14, 0))
+ .checkOut(LocalTime.of(12, 0))
+ .price(
+ RoomPrice.builder()
+ .offWeekDaysMinFee(75000)
+ .offWeekendMinFee(85000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(120000)
+ .build()
+ )
+ .roomOption(
+ RoomOption.builder()
+ .cooking(true)
+ .airCondition(true)
+ .bath(true)
+ .bathFacility(true)
+ .pc(false)
+ .diningTable(true)
+ .hairDryer(true)
+ .homeTheater(false)
+ .internet(true)
+ .cable(false)
+ .refrigerator(true)
+ .sofa(true)
+ .toiletries(true)
+ .tv(true)
+ .build()
+ )
+ .build()
+ );
+ }
+
+ roomRepository.save(
+ Room.builder()
+ .product(products.get(0))
+ .name("객실1")
+ .code(1001)
+ .description("객실1 설명")
+ .standard(2)
+ .capacity(4)
+ .checkIn(LocalTime.of(14, 0))
+ .checkOut(LocalTime.of(12, 0))
+ .price(
+ RoomPrice.builder()
+ .offWeekDaysMinFee(75000)
+ .offWeekendMinFee(85000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(120000)
+ .build()
+ )
+ .roomOption(
+ RoomOption.builder()
+ .cooking(true)
+ .airCondition(true)
+ .bath(true)
+ .bathFacility(true)
+ .pc(false)
+ .diningTable(true)
+ .hairDryer(true)
+ .homeTheater(false)
+ .internet(true)
+ .cable(false)
+ .refrigerator(true)
+ .sofa(true)
+ .toiletries(true)
+ .tv(true)
+ .build()
+ )
+ .build()
+ );
+ }
+
@Test
void checkAvailable() throws InterruptedException {
int poolSize = 2;
@@ -32,26 +194,90 @@ void checkAvailable() throws InterruptedException {
ExecutorService executors = Executors.newFixedThreadPool(poolSize);
CountDownLatch latch = new CountDownLatch(threadSize);
- PreoccupyRoomsRequestDto request1 = new PreoccupyRoomsRequestDto(List.of(
- new PreoccupyRoomItemRequestDto(1L, "2024-03-05", "2024-03-08"),
- new PreoccupyRoomItemRequestDto(2L, "2024-04-05", "2024-04-09")
- ));
- PreoccupyRoomsRequestDto request2 = new PreoccupyRoomsRequestDto(List.of(
- new PreoccupyRoomItemRequestDto(1L, "2024-03-06", "2024-03-09"),
- new PreoccupyRoomItemRequestDto(2L, "2024-04-04", "2024-04-06")
- ));
- PreoccupyRoomsRequestDto request3 = new PreoccupyRoomsRequestDto(List.of(
- new PreoccupyRoomItemRequestDto(1L, "2024-02-10", "2024-02-15"),
- new PreoccupyRoomItemRequestDto(3L, "2024-04-05", "2024-04-09")
- ));
- PreoccupyRoomsRequestDto request4 = new PreoccupyRoomsRequestDto(List.of(
- new PreoccupyRoomItemRequestDto(3L, "2024-04-04", "2024-04-08")
- ));
- PreoccupyRoomsRequestDto request5 = new PreoccupyRoomsRequestDto(List.of(
- new PreoccupyRoomItemRequestDto(5L, "2024-03-05", "2024-03-08"),
- new PreoccupyRoomItemRequestDto(6L, "2024-04-05", "2024-04-09"),
- new PreoccupyRoomItemRequestDto(7L, "2024-04-05", "2024-04-09")
- ));
+ PreoccupyRoomsRequestDto request1 = PreoccupyRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1001L)
+ .startDate("2024-03-06")
+ .endDate("2024-03-09")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1002L)
+ .startDate("2024-04-04")
+ .endDate("2024-04-06")
+ .build()
+ )
+ )
+ .build();
+
+ PreoccupyRoomsRequestDto request2 = PreoccupyRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1001L)
+ .startDate("2024-03-05")
+ .endDate("2024-03-08")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1001L)
+ .startDate("2024-03-05")
+ .endDate("2024-03-08")
+ .build()
+ )
+ )
+ .build();
+
+ PreoccupyRoomsRequestDto request3 = PreoccupyRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1001L)
+ .startDate("2024-02-10")
+ .endDate("2024-02-15")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1003L)
+ .startDate("2024-04-05")
+ .endDate("2024-04-09")
+ .build()
+ )
+ )
+ .build();
+
+ PreoccupyRoomsRequestDto request4 = PreoccupyRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1003L)
+ .startDate("2024-04-04")
+ .endDate("2024-04-08")
+ .build()
+ )
+ )
+ .build();
+
+ PreoccupyRoomsRequestDto request5 = PreoccupyRoomsRequestDto.builder()
+ .rooms(
+ List.of(
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1001L)
+ .startDate("2024-02-10")
+ .endDate("2024-02-15")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1003L)
+ .startDate("2024-02-05")
+ .endDate("2024-02-09")
+ .build(),
+ PreoccupyRoomItemRequestDto.builder()
+ .roomCode(1002L)
+ .startDate("2024-05-10")
+ .endDate("2024-05-12")
+ .build()
+ )
+ )
+ .build();
executors.submit(() -> {
StopWatch stopWatch = threadLocalStopWatch.get();
@@ -59,7 +285,7 @@ void checkAvailable() throws InterruptedException {
stopWatch.start();
preoccupyRoomsLockFacade.checkAvailableAndPreoccupy(1L, request1);
} catch (Exception e) {
- System.out.println(e.getMessage());
+ log.error(e.getMessage());
} finally {
stopWatch.stop();
log.info("{} ::: {} ms", "request1", stopWatch.getTotalTimeMillis());
@@ -74,7 +300,7 @@ void checkAvailable() throws InterruptedException {
stopWatch.start();
preoccupyRoomsLockFacade.checkAvailableAndPreoccupy(2L, request2);
} catch (Exception e) {
- System.out.println(e.getMessage());
+ log.error(e.getMessage());
} finally {
stopWatch.stop();
log.info("{} ::: {} ms", "request2", stopWatch.getTotalTimeMillis());
@@ -89,7 +315,7 @@ void checkAvailable() throws InterruptedException {
stopWatch.start();
preoccupyRoomsLockFacade.checkAvailableAndPreoccupy(3L, request3);
} catch (Exception e) {
- System.out.println(e.getMessage());
+ log.error(e.getMessage());
} finally {
stopWatch.stop();
log.info("{} ::: {} ms", "request3", stopWatch.getTotalTimeMillis());
@@ -104,7 +330,7 @@ void checkAvailable() throws InterruptedException {
stopWatch.start();
preoccupyRoomsLockFacade.checkAvailableAndPreoccupy(4L, request4);
} catch (Exception e) {
- System.out.println(e.getMessage());
+ log.error(e.getMessage());
} finally {
stopWatch.stop();
log.info("{} ::: {} ms", "request4", stopWatch.getTotalTimeMillis());
@@ -119,7 +345,7 @@ void checkAvailable() throws InterruptedException {
stopWatch.start();
preoccupyRoomsLockFacade.checkAvailableAndPreoccupy(5L, request5);
} catch (Exception e) {
- System.out.println(e.getMessage());
+ log.error(e.getMessage());
} finally {
stopWatch.stop();
log.info("{} ::: {} ms", "request5", stopWatch.getTotalTimeMillis());
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/facade/ReservationLockFacadeTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/facade/ReservationLockFacadeTest.java
index 458b60ee..5aaea371 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/facade/ReservationLockFacadeTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/facade/ReservationLockFacadeTest.java
@@ -1,27 +1,39 @@
package com.fc.shimpyo_be.domain.reservation.unit.facade;
import com.fc.shimpyo_be.config.AbstractContainersSupport;
+import com.fc.shimpyo_be.config.DatabaseCleanUp;
+import com.fc.shimpyo_be.config.TestDBCleanerConfig;
+import com.fc.shimpyo_be.domain.member.entity.Authority;
+import com.fc.shimpyo_be.domain.member.entity.Member;
+import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
+import com.fc.shimpyo_be.domain.product.entity.*;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
import com.fc.shimpyo_be.domain.reservation.dto.request.SaveReservationRequestDto;
import com.fc.shimpyo_be.domain.reservation.entity.PayMethod;
import com.fc.shimpyo_be.domain.reservation.facade.ReservationLockFacade;
import com.fc.shimpyo_be.domain.reservationproduct.dto.request.ReservationProductRequestDto;
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
+import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.util.StopWatch;
import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
@Slf4j
+@Import(TestDBCleanerConfig.class)
@SpringBootTest
class ReservationLockFacadeTest extends AbstractContainersSupport {
@@ -31,6 +43,22 @@ class ReservationLockFacadeTest extends AbstractContainersSupport {
@Autowired
private RedisTemplate redisTemplate;
+ @Autowired
+ private DatabaseCleanUp databaseCleanUp;
+
+ @Autowired
+ private MemberRepository memberRepository;
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ @Autowired
+ private RoomRepository roomRepository;
+
+ private final String[] tableNameArray = {
+ "member", "product", "room", "product_option", "address", "room_option", "amenity"
+ };
+
private static final ThreadLocal threadLocalStopWatch = ThreadLocal.withInitial(StopWatch::new);
@BeforeEach
@@ -41,6 +69,115 @@ void setUp() {
long memberId4 = 4L;
long memberId5 = 5L;
+ databaseCleanUp.cleanUp(tableNameArray);
+
+ for (int i = 1; i <= 5; i++) {
+ String name = "member" + i;
+ memberRepository.save(
+ Member.builder()
+ .email(name + "@email.com")
+ .name(name)
+ .password("password")
+ .photoUrl("member photo url")
+ .authority(Authority.ROLE_USER)
+ .build()
+ );
+ }
+
+ List products = new ArrayList<>();
+ for (int i = 1; i <= 3; i++) {
+ String productName = "호텔" + i;
+ float starAvg = ThreadLocalRandom.current().nextFloat(0, 5);
+ String infoCenter = String.format("02-1234-%d%d%d%d", i, i, i, i);
+ products.add(
+ productRepository.save(
+ Product.builder()
+ .name(productName)
+ .thumbnail(productName + " 썸네일 url")
+ .description(productName + " 설명")
+ .starAvg(starAvg)
+ .category(Category.TOURIST_HOTEL)
+ .address(
+ Address.builder()
+ .address(productName + " 주소")
+ .detailAddress(productName + " 상세 주소")
+ .mapX(1.0)
+ .mapY(1.5)
+ .build()
+ )
+ .productOption(
+ ProductOption.builder()
+ .cooking(true)
+ .foodPlace("음료 가능")
+ .parking(true)
+ .pickup(false)
+ .infoCenter(infoCenter)
+ .build()
+ )
+ .amenity(
+ Amenity.builder()
+ .barbecue(false)
+ .beauty(true)
+ .beverage(true)
+ .fitness(true)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(true)
+ .publicBath(true)
+ .publicPc(true)
+ .seminar(false)
+ .sports(false)
+ .build()
+ )
+ .build()
+ )
+ );
+ }
+
+ List rooms = new ArrayList<>();
+ for (int i = 1; i <= 5; i++) {
+ String roomName = "객실" + i;
+ rooms.add(
+ roomRepository.save(
+ Room.builder()
+ .product(products.get((i - 1) % 3))
+ .name(roomName)
+ .description(roomName + " 설명")
+ .standard(2)
+ .capacity(4)
+ .checkIn(LocalTime.of(14, 0))
+ .checkOut(LocalTime.of(12, 0))
+ .price(
+ RoomPrice.builder()
+ .offWeekDaysMinFee(75000)
+ .offWeekendMinFee(85000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(120000)
+ .build()
+ )
+ .roomOption(
+ RoomOption.builder()
+ .cooking(true)
+ .airCondition(true)
+ .bath(true)
+ .bathFacility(true)
+ .pc(false)
+ .diningTable(true)
+ .hairDryer(true)
+ .homeTheater(false)
+ .internet(true)
+ .cable(false)
+ .refrigerator(true)
+ .sofa(true)
+ .toiletries(true)
+ .tv(true)
+ .build()
+ )
+ .build()
+ )
+ );
+ }
+
saveRedisData(memberId1, 2L, LocalDate.of(2023, 11, 20), LocalDate.of(2023, 11, 23));
saveRedisData(memberId1, 3L, LocalDate.of(2023, 11, 24), LocalDate.of(2023, 11, 26));
@@ -68,36 +205,47 @@ void saveReservation_test() throws InterruptedException {
SaveReservationRequestDto request1 = new SaveReservationRequestDto(
List.of(
- getReservationProductRequestData(2L, "2023-11-20", "2023-11-23"),
- getReservationProductRequestData(3L, "2023-11-24", "2023-11-26")
+ new ReservationProductRequestDto(1L, 2L, "2023-11-20", "2023-11-23",
+ "visitor1", "010-1111-1111", 100000),
+ new ReservationProductRequestDto(2L, 3L, "2023-11-24", "2023-11-26",
+ "visitor2", "010-2222-2222", 150000)
), PayMethod.KAKAO_PAY, 250000
);
SaveReservationRequestDto request2 = new SaveReservationRequestDto(
- List.of(getReservationProductRequestData(4L, "2023-10-10", "2023-10-14")),
+ List.of(new ReservationProductRequestDto(3L, 4L, "2023-10-10", "2023-10-14",
+ "visitor3", "010-3333-3333", 125000)),
PayMethod.CREDIT_CARD, 125000
);
SaveReservationRequestDto request3 = new SaveReservationRequestDto(
List.of(
- getReservationProductRequestData(2L, "2023-11-10", "2023-11-14"),
- getReservationProductRequestData(5L, "2023-10-17", "2023-10-20"),
- getReservationProductRequestData(3L, "2023-10-10", "2023-10-14")
+ new ReservationProductRequestDto(4L, 2L, "2023-11-10", "2023-11-14",
+ "visitor", "010-1111-1111", 90000),
+ new ReservationProductRequestDto(5L, 5L, "2023-10-17", "2023-10-20",
+ "visitor", "010-1111-1111", 110000),
+ new ReservationProductRequestDto(6L, 3L, "2023-10-10", "2023-10-14",
+ "visitor", "010-1111-1111", 100000)
), PayMethod.NAVER_PAY, 300000
);
SaveReservationRequestDto request4 = new SaveReservationRequestDto(
List.of(
- getReservationProductRequestData(4L, "2023-10-05", "2023-10-07"),
- getReservationProductRequestData(5L, "2023-10-10", "2023-10-14")
- ), PayMethod.KAKAO_PAY, 250000
+ new ReservationProductRequestDto(7L, 4L, "2023-10-05", "2023-10-07",
+ "visitor", "010-1111-1111", 125000),
+ new ReservationProductRequestDto(8L, 5L, "2023-10-10", "2023-10-14",
+ "visitor", "010-1111-1111", 125000)
+ ), PayMethod.KAKAO_PAY, 250000
);
SaveReservationRequestDto request5 = new SaveReservationRequestDto(
List.of(
- getReservationProductRequestData(1L, "2023-10-10", "2023-10-14"),
- getReservationProductRequestData(2L, "2023-12-11", "2023-12-15"),
- getReservationProductRequestData(4L, "2023-11-11", "2023-11-14")
+ new ReservationProductRequestDto(9L, 1L, "2023-10-10", "2023-10-14",
+ "visitor4", "010-4444-4444", 150000),
+ new ReservationProductRequestDto(10L, 2L, "2023-12-11", "2023-12-15",
+ "visitor4", "010-4444-4444", 150000),
+ new ReservationProductRequestDto(11L, 4L, "2023-11-11", "2023-11-14",
+ "visitor4", "010-4444-4444", 100000)
), PayMethod.PAYPAL, 400000
);
@@ -185,26 +333,8 @@ private void saveRedisData(long memberId, long roomId, LocalDate start, LocalDat
while (start.isBefore(end)) {
String key = String.format(keyFormat, roomId, start);
- valueOperations.set(key, String.valueOf(memberId), 50, TimeUnit.SECONDS);
+ valueOperations.set(key, String.valueOf(memberId), 60, TimeUnit.SECONDS);
start = start.plusDays(1);
}
}
-
- private ReservationProductRequestDto getReservationProductRequestData(long roomId, String startDate, String endDate) {
- String defaultValue = "DEFAULT_VALUE";
- return new ReservationProductRequestDto(
- roomId,
- defaultValue,
- defaultValue,
- 2,
- 4,
- startDate,
- endDate,
- defaultValue,
- defaultValue,
- defaultValue,
- defaultValue,
- 100000
- );
- }
}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/repository/ReservationRepositoryTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/repository/ReservationRepositoryTest.java
index 065f87f6..cb070e90 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/repository/ReservationRepositoryTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/repository/ReservationRepositoryTest.java
@@ -1,34 +1,200 @@
package com.fc.shimpyo_be.domain.reservation.unit.repository;
+import com.fc.shimpyo_be.config.DatabaseCleanUp;
+import com.fc.shimpyo_be.config.TestDBCleanerConfig;
import com.fc.shimpyo_be.config.TestQuerydslConfig;
+import com.fc.shimpyo_be.domain.member.entity.Authority;
+import com.fc.shimpyo_be.domain.member.entity.Member;
+import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
+import com.fc.shimpyo_be.domain.product.entity.*;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
+import com.fc.shimpyo_be.domain.reservation.entity.PayMethod;
+import com.fc.shimpyo_be.domain.reservation.entity.Reservation;
import com.fc.shimpyo_be.domain.reservation.repository.ReservationRepository;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
+import com.fc.shimpyo_be.domain.reservationproduct.entity.ReservationProduct;
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
+import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
+import org.junit.jupiter.api.*;
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.test.context.jdbc.Sql;
+import java.time.LocalDate;
+import java.time.LocalTime;
import java.util.List;
-import static org.assertj.core.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
-
-@Import(TestQuerydslConfig.class)
+@Import({TestQuerydslConfig.class, TestDBCleanerConfig.class})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@DataJpaTest
public class ReservationRepositoryTest {
+ @Autowired
+ private DatabaseCleanUp databaseCleanUp;
+
@Autowired
private ReservationRepository reservationRepository;
- @Sql("classpath:testdata/reservation-repository-setup.sql")
+ @Autowired
+ private MemberRepository memberRepository;
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ @Autowired
+ private RoomRepository roomRepository;
+
+ private final String[] tableNameArray = {
+ "member", "product", "room", "product_option",
+ "address", "room_option", "amenity", "reservation_product", "reservation"
+ };
+
+ private Member member;
+
+ @BeforeEach
+ void setUp() {
+ databaseCleanUp.cleanUp(tableNameArray);
+
+ member = memberRepository.save(
+ Member.builder()
+ .email("member@email.com")
+ .name("member")
+ .password("password")
+ .photoUrl("member photo url")
+ .authority(Authority.ROLE_USER)
+ .build()
+ );
+
+ Product product = productRepository.save(
+ Product.builder()
+ .name("호텔1")
+ .thumbnail("호텔1 썸네일 url")
+ .description("호텔1 설명")
+ .starAvg(4.0f)
+ .category(Category.TOURIST_HOTEL)
+ .address(
+ Address.builder()
+ .address("호텔1 주소")
+ .detailAddress("호텔1 상세 주소")
+ .mapX(10000)
+ .mapY(11000)
+ .build()
+ )
+ .productOption(
+ ProductOption.builder()
+ .cooking(true)
+ .foodPlace("음료 가능")
+ .parking(true)
+ .pickup(false)
+ .infoCenter("031-222-333")
+ .build()
+ )
+ .amenity(
+ Amenity.builder()
+ .barbecue(false)
+ .beauty(true)
+ .beverage(true)
+ .fitness(true)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(true)
+ .publicBath(true)
+ .publicPc(true)
+ .seminar(false)
+ .sports(false)
+ .build()
+ )
+ .build()
+ );
+
+ Room room = roomRepository.save(
+ Room.builder()
+ .product(product)
+ .name("객실1")
+ .description("객실1 설명")
+ .standard(2)
+ .capacity(4)
+ .checkIn(LocalTime.of(14, 0))
+ .checkOut(LocalTime.of(12, 0))
+ .price(
+ RoomPrice.builder()
+ .offWeekDaysMinFee(75000)
+ .offWeekendMinFee(85000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(120000)
+ .build()
+ )
+ .roomOption(
+ RoomOption.builder()
+ .cooking(true)
+ .airCondition(true)
+ .bath(true)
+ .bathFacility(true)
+ .pc(false)
+ .diningTable(true)
+ .hairDryer(true)
+ .homeTheater(false)
+ .internet(true)
+ .cable(false)
+ .refrigerator(true)
+ .sofa(true)
+ .toiletries(true)
+ .tv(true)
+ .build()
+ )
+ .build()
+ );
+
+ Reservation reservation1 = reservationRepository.save(
+ Reservation.builder()
+ .reservationProducts(
+ List.of(
+ ReservationProduct.builder()
+ .room(room)
+ .startDate(LocalDate.of(2023, 11, 10))
+ .endDate(LocalDate.of(2023, 11, 12))
+ .visitorName("방문자명")
+ .visitorPhone("010-1111-1111")
+ .price(200000)
+ .build()
+ )
+ )
+ .member(member)
+ .payMethod(PayMethod.CREDIT_CARD)
+ .totalPrice(200000)
+ .build()
+ );
+
+ Reservation reservation2 = reservationRepository.save(
+ Reservation.builder()
+ .reservationProducts(
+ List.of(
+ ReservationProduct.builder()
+ .room(room)
+ .startDate(LocalDate.of(2023, 12, 4))
+ .endDate(LocalDate.of(2023, 12, 7))
+ .visitorName("방문자명")
+ .visitorPhone("010-1111-1111")
+ .price(300000)
+ .build()
+ )
+ )
+ .totalPrice(300000)
+ .payMethod(PayMethod.KAKAO_PAY)
+ .member(member)
+ .build()
+ );
+ }
+
@DisplayName("findIdsByMemberId 테스트")
@Test
void findIdsByMemberId_test() {
//given
- long memberId = 1;
+ long memberId = member.getId();
//when
List result = reservationRepository.findIdsByMemberId(memberId);
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/service/GetReservationListServiceTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/service/GetReservationListServiceTest.java
index 5dd03ee5..b87db936 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/service/GetReservationListServiceTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/service/GetReservationListServiceTest.java
@@ -1,28 +1,43 @@
package com.fc.shimpyo_be.domain.reservation.unit.service;
import com.fc.shimpyo_be.config.AbstractContainersSupport;
+import com.fc.shimpyo_be.config.DatabaseCleanUp;
+import com.fc.shimpyo_be.config.TestDBCleanerConfig;
+import com.fc.shimpyo_be.domain.member.entity.Authority;
+import com.fc.shimpyo_be.domain.member.entity.Member;
+import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
+import com.fc.shimpyo_be.domain.product.entity.*;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
import com.fc.shimpyo_be.domain.reservation.dto.response.ReservationInfoResponseDto;
+import com.fc.shimpyo_be.domain.reservation.entity.PayMethod;
+import com.fc.shimpyo_be.domain.reservation.entity.Reservation;
import com.fc.shimpyo_be.domain.reservation.repository.ReservationRepository;
import com.fc.shimpyo_be.domain.reservation.service.ReservationService;
-import com.fc.shimpyo_be.domain.reservationproduct.repository.ReservationProductRepository;
+import com.fc.shimpyo_be.domain.reservationproduct.entity.ReservationProduct;
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
+import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
import lombok.extern.slf4j.Slf4j;
-import org.junit.jupiter.api.*;
+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.context.SpringBootTest;
-import org.springframework.core.io.ClassPathResource;
+import org.springframework.context.annotation.Import;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
-import org.springframework.jdbc.datasource.init.ScriptUtils;
-import org.springframework.test.context.jdbc.Sql;
-import javax.sql.DataSource;
-import java.sql.Connection;
-import java.sql.SQLException;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
-import static org.assertj.core.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
@Slf4j
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@Import(TestDBCleanerConfig.class)
@SpringBootTest
public class GetReservationListServiceTest extends AbstractContainersSupport {
@@ -33,36 +48,236 @@ public class GetReservationListServiceTest extends AbstractContainersSupport {
private ReservationRepository reservationRepository;
@Autowired
- private ReservationProductRepository reservationProductRepository;
+ private MemberRepository memberRepository;
- @BeforeAll
- void setUp(@Autowired DataSource dataSource) throws SQLException {
- try (Connection conn = dataSource.getConnection()) {
- ScriptUtils
- .executeSqlScript(conn, new ClassPathResource("testdata/reservation-service-setup.sql"));
- }
+ @Autowired
+ private ProductRepository productRepository;
+
+ @Autowired
+ private RoomRepository roomRepository;
+
+ @Autowired
+ private DatabaseCleanUp databaseCleanUp;
+
+ private Member member;
+
+ private final String[] tableNameArray = {
+ "member", "product", "room", "product_option",
+ "address", "room_option", "amenity", "reservation_product", "reservation"
+ };
+
+ @BeforeEach
+ void setUp() {
+ databaseCleanUp.cleanUp(tableNameArray);
+
+ member = memberRepository.save(
+ Member.builder()
+ .email("member@email.com")
+ .name("member")
+ .password("password")
+ .photoUrl("member photo url")
+ .authority(Authority.ROLE_USER)
+ .build()
+ );
+
+ List products = getProductTestDataList(3);
+ List rooms = getRoomTestDataList(5, products);
+
+ Reservation reservation1 = reservationRepository.save(
+ Reservation.builder()
+ .reservationProducts(
+ List.of(
+ getReservationProduct(
+ rooms.get(0),
+ LocalDate.of(2023, 11, 10),
+ LocalDate.of(2023, 11, 12),
+ 200000
+ ),
+ getReservationProduct(
+ rooms.get(1),
+ LocalDate.of(2023, 11, 20),
+ LocalDate.of(2023, 11, 22),
+ 200000
+ )
+ )
+ )
+ .member(member)
+ .payMethod(PayMethod.CREDIT_CARD)
+ .totalPrice(400000)
+ .build()
+ );
+
+ Reservation reservation2 = reservationRepository.save(
+ Reservation.builder()
+ .reservationProducts(
+ List.of(
+ getReservationProduct(
+ rooms.get(2),
+ LocalDate.of(2023, 12, 4),
+ LocalDate.of(2023, 12, 7),
+ 300000
+ )
+ )
+ )
+ .totalPrice(300000)
+ .payMethod(PayMethod.KAKAO_PAY)
+ .member(member)
+ .build()
+ );
+
+ Reservation reservation3 = reservationRepository.save(
+ Reservation.builder()
+ .reservationProducts(
+ List.of(
+ getReservationProduct(
+ rooms.get(1),
+ LocalDate.of(2023, 12, 15),
+ LocalDate.of(2023, 12, 18),
+ 360000
+ ),
+ getReservationProduct(
+ rooms.get(2),
+ LocalDate.of(2024, 1, 10),
+ LocalDate.of(2024, 1, 12),
+ 240000
+ )
+ )
+ )
+ .member(member)
+ .payMethod(PayMethod.CREDIT_CARD)
+ .totalPrice(600000)
+ .build()
+ );
}
- @Sql("classpath:testdata/reservation-service-insert.sql")
@DisplayName("전체 주문 목록을 정상적으로 페이징 조회할 수 있다.")
@Test
void getReservationInfoList_test() {
//given
- long memberId = 1;
+ long memberId = member.getId();
PageRequest pageRequest = PageRequest.of(0, 2);
//when
Page result = reservationService.getReservationInfoList(memberId, pageRequest);
//then
- assertThat(result.getTotalElements()).isEqualTo(3);
- assertThat(result.getTotalPages()).isEqualTo(2);
+ assertThat(result.getTotalElements()).isEqualTo(5);
+ assertThat(result.getTotalPages()).isEqualTo(3);
log.info("{}", result.getContent().get(0));
}
- @AfterEach
- void tearDown() {
- reservationProductRepository.deleteAll();
- reservationRepository.deleteAll();
+ private ReservationProduct getReservationProduct(Room room, LocalDate startDate, LocalDate endDate, int price) {
+ return ReservationProduct.builder()
+ .room(room)
+ .startDate(startDate)
+ .endDate(endDate)
+ .visitorName("방문자명")
+ .visitorPhone("010-1111-1111")
+ .price(price)
+ .build();
+ }
+
+ private List getProductTestDataList(int size) {
+ List products = new ArrayList<>();
+
+ for (int i = 1; i <= size; i++) {
+ String productName = "호텔" + i;
+ float starAvg = ThreadLocalRandom.current().nextFloat(0, 5);
+ String infoCenter = String.format("02-1234-%d%d%d%d", i, i, i, i);
+ products.add(
+ productRepository.save(
+ Product.builder()
+ .name(productName)
+ .thumbnail(productName + " 썸네일 url")
+ .description(productName + " 설명")
+ .starAvg(starAvg)
+ .category(Category.TOURIST_HOTEL)
+ .address(
+ Address.builder()
+ .address(productName + " 주소")
+ .detailAddress(productName + " 상세 주소")
+ .mapX(1.0)
+ .mapY(1.5)
+ .build()
+ )
+ .productOption(
+ ProductOption.builder()
+ .cooking(true)
+ .foodPlace("음료 가능")
+ .parking(true)
+ .pickup(false)
+ .infoCenter(infoCenter)
+ .build()
+ )
+ .amenity(
+ Amenity.builder()
+ .barbecue(false)
+ .beauty(true)
+ .beverage(true)
+ .fitness(true)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(true)
+ .publicBath(true)
+ .publicPc(true)
+ .seminar(false)
+ .sports(false)
+ .build()
+ )
+ .build()
+ )
+ );
+ }
+
+ return products;
+ }
+
+ private List getRoomTestDataList(int size, List products) {
+ List rooms = new ArrayList<>();
+ for (int i = 1; i <= size; i++) {
+ String roomName = "객실" + i;
+ rooms.add(
+ roomRepository.save(
+ Room.builder()
+ .code(1000 + i)
+ .product(products.get((i - 1) % products.size()))
+ .name(roomName)
+ .description(roomName + " 설명")
+ .standard(2)
+ .capacity(4)
+ .checkIn(LocalTime.of(14, 0))
+ .checkOut(LocalTime.of(12, 0))
+ .price(
+ RoomPrice.builder()
+ .offWeekDaysMinFee(75000)
+ .offWeekendMinFee(85000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(120000)
+ .build()
+ )
+ .roomOption(
+ RoomOption.builder()
+ .cooking(true)
+ .airCondition(true)
+ .bath(true)
+ .bathFacility(true)
+ .pc(false)
+ .diningTable(true)
+ .hairDryer(true)
+ .homeTheater(false)
+ .internet(true)
+ .cable(false)
+ .refrigerator(true)
+ .sofa(true)
+ .toiletries(true)
+ .tv(true)
+ .build()
+ )
+ .build()
+ )
+ );
+ }
+
+ return rooms;
}
}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/service/ReservationServiceTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/service/ReservationServiceTest.java
index 9ef9533c..30cb9cec 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/service/ReservationServiceTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservation/unit/service/ReservationServiceTest.java
@@ -1,209 +1,373 @@
package com.fc.shimpyo_be.domain.reservation.unit.service;
+import com.fc.shimpyo_be.config.AbstractContainersSupport;
+import com.fc.shimpyo_be.config.DatabaseCleanUp;
+import com.fc.shimpyo_be.config.TestDBCleanerConfig;
+import com.fc.shimpyo_be.domain.cart.entity.Cart;
+import com.fc.shimpyo_be.domain.cart.repository.CartRepository;
import com.fc.shimpyo_be.domain.member.entity.Authority;
import com.fc.shimpyo_be.domain.member.entity.Member;
import com.fc.shimpyo_be.domain.member.exception.MemberNotFoundException;
import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
-import com.fc.shimpyo_be.domain.product.exception.RoomNotFoundException;
+import com.fc.shimpyo_be.domain.product.entity.*;
+import com.fc.shimpyo_be.domain.room.exception.RoomNotFoundException;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
import com.fc.shimpyo_be.domain.reservation.dto.request.SaveReservationRequestDto;
import com.fc.shimpyo_be.domain.reservation.dto.response.SaveReservationResponseDto;
import com.fc.shimpyo_be.domain.reservation.entity.PayMethod;
-import com.fc.shimpyo_be.domain.reservation.entity.Reservation;
-import com.fc.shimpyo_be.domain.reservation.repository.ReservationRepository;
import com.fc.shimpyo_be.domain.reservation.service.ReservationService;
import com.fc.shimpyo_be.domain.reservationproduct.dto.request.ReservationProductRequestDto;
-import com.fc.shimpyo_be.domain.reservationproduct.repository.ReservationProductRepository;
import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
+import com.fc.shimpyo_be.global.util.DateTimeUtil;
+import lombok.extern.slf4j.Slf4j;
+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.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
-import java.util.Optional;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
import static org.assertj.core.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.BDDMockito.willThrow;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-@ExtendWith(MockitoExtension.class)
-public class ReservationServiceTest {
+@Slf4j
+@Import(TestDBCleanerConfig.class)
+@SpringBootTest
+public class ReservationServiceTest extends AbstractContainersSupport {
- @InjectMocks
+ @Autowired
private ReservationService reservationService;
- @Mock
- private ReservationRepository reservationRepository;
-
- @Mock
- private ReservationProductRepository reservationProductRepository;
-
- @Mock
+ @Autowired
private MemberRepository memberRepository;
- @Mock
+ @Autowired
+ private ProductRepository productRepository;
+
+ @Autowired
private RoomRepository roomRepository;
- @Mock
- private RedisTemplate redisTemplate;
+ @Autowired
+ private CartRepository cartRepository;
+
+ @Autowired
+ private DatabaseCleanUp databaseCleanUp;
+
+ private Member member;
+
+ private final String[] tableNameArray = {
+ "member", "product", "room", "product_option", "address", "room_option", "amenity", "cart"
+ };
+
+ private LocalDate startDate1 = LocalDate.now().plusDays(1);
+ private LocalDate endDate1 = startDate1.plusDays(2);
+
+ private LocalDate startDate2 = LocalDate.now().plusMonths(2);
+ private LocalDate endDate2 = startDate2.plusDays(3);
+
+ @BeforeEach
+ void setUp() {
+ databaseCleanUp.cleanUp(tableNameArray);
+
+ member = memberRepository.save(
+ Member.builder()
+ .email("member@email.com")
+ .name("member")
+ .password("password")
+ .photoUrl("member photo url")
+ .authority(Authority.ROLE_USER)
+ .build()
+ );
+
+ List products = getProductTestDataList(3);
+ List rooms = getRoomTestDataList(3, products);
+
+ cartRepository.save(
+ Cart.builder()
+ .member(member)
+ .roomCode(rooms.get(0).getCode())
+ .startDate(startDate1)
+ .endDate(endDate1)
+ .price(150000L)
+ .build()
+ );
- private final Member member
- = Member.builder()
- .id(1L)
- .name("member")
- .password("password")
- .authority(Authority.ROLE_USER)
- .photoUrl("photoUrl")
- .email("email")
- .build();
+ cartRepository.save(
+ Cart.builder()
+ .member(member)
+ .roomCode(rooms.get(1).getCode())
+ .startDate(startDate2)
+ .endDate(endDate2)
+ .price(200000L)
+ .build()
+ );
+ }
@DisplayName("정상적으로 예약을 저장할 수 있다.")
@Test
void saveReservation_test() {
//given
- long memberId = 1L;
+ long memberId = member.getId();
long roomId1 = 1L;
long roomId2 = 2L;
- Room room = Room.builder()
- .id(1L)
- .name("room1")
- .description("description")
- .price(50000)
- .build();
-
SaveReservationRequestDto requestDto
= new SaveReservationRequestDto(
List.of(
- getReservationProductRequestData(
- roomId1, "2023-11-20", "2023-11-23",
+ new ReservationProductRequestDto(
+ 1L, roomId1, startDate1.toString(), endDate1.toString(),
"visitor1", "010-1111-1111", 150000),
- getReservationProductRequestData(
- roomId2, "2023-11-18", "2023-11-20",
- "visitor2", "010-2222-2222", 200000)
+ new ReservationProductRequestDto(
+ 2L, roomId2, startDate2.toString(), endDate2.toString(),
+ "visitor1", "010-1111-1111", 200000
+ )
), PayMethod.CREDIT_CARD, 350000
);
- Reservation reservation = Reservation.builder()
- .id(1L)
- .totalPrice(150000)
- .payMethod(PayMethod.CREDIT_CARD)
- .build();
-
- given(memberRepository.findById(anyLong()))
- .willReturn(Optional.of(member));
- given(roomRepository.findById(anyLong()))
- .willReturn(Optional.of(room));
- given(reservationRepository.save(any(Reservation.class)))
- .willReturn(reservation);
+ Map> map = new HashMap<>();
+ for (ReservationProductRequestDto reservationProduct : requestDto.reservationProducts()) {
+ map.put(
+ reservationProduct.roomId(),
+ getKeyList(reservationProduct.roomId(), reservationProduct.startDate(), reservationProduct.endDate())
+ );
+ }
//when
- SaveReservationResponseDto result = reservationService.saveReservation(memberId, requestDto);
+ SaveReservationResponseDto result = reservationService.saveReservation(memberId, requestDto, map);
//then
assertThat(result.reservationId()).isNotNull();
+ assertThat(result.reservationProducts()).hasSize(2);
+ assertThat(cartRepository.findById(1L)).isNotPresent();
+ assertThat(cartRepository.findById(2L)).isNotPresent();
+ }
+
+ @DisplayName("예약 저장시 장바구니 식별자가 -1인 경우는 장바구니 아이템을 삭제하지 않는다.")
+ @Test
+ void saveReservation_cart_delete_filter_test() {
+ //given
+ long memberId = member.getId();
+ long roomId1 = 1L;
+ long cartId1 = -1L;
+
+ SaveReservationRequestDto requestDto
+ = new SaveReservationRequestDto(
+ List.of(
+ new ReservationProductRequestDto(
+ cartId1, roomId1, startDate1.toString(), endDate1.toString(),
+ "visitor1", "010-1111-1111", 150000)
+ ), PayMethod.CREDIT_CARD, 150000
+ );
- verify(memberRepository, times(1)).findById(anyLong());
- verify(roomRepository, times(2)).findById(anyLong());
- verify(reservationRepository, times(1)).save(any(Reservation.class));
+ Map> map = new HashMap<>();
+ for (ReservationProductRequestDto reservationProduct : requestDto.reservationProducts()) {
+ map.put(
+ reservationProduct.roomId(),
+ getKeyList(reservationProduct.roomId(), reservationProduct.startDate(), reservationProduct.endDate())
+ );
+ }
+
+ //when
+ SaveReservationResponseDto result = reservationService.saveReservation(memberId, requestDto, map);
+
+ //then
+ assertThat(result.reservationId()).isNotNull();
+ assertThat(result.reservationProducts()).hasSize(1);
+ assertThat(cartRepository.findAll()).hasSize(2);
}
@DisplayName("회원이 존재하지 않으면 예약을 저장할 수 없다.")
@Test
void saveReservation_memberNotFound_test() {
//given
- long memberId = 1L;
+ long memberId = 1000L;
long roomId1 = 1L;
long roomId2 = 2L;
SaveReservationRequestDto requestDto
= new SaveReservationRequestDto(
List.of(
- getReservationProductRequestData(
- roomId1, "2023-11-20", "2023-11-23",
+ new ReservationProductRequestDto(
+ 1L, roomId1, startDate1.toString(), endDate1.toString(),
"visitor1", "010-1111-1111", 150000),
- getReservationProductRequestData(
- roomId2, "2023-11-18", "2023-11-20",
- "visitor2", "010-2222-2222", 200000)
+ new ReservationProductRequestDto(
+ 2L, roomId2, startDate2.toString(), endDate2.toString(),
+ "visitor1", "010-1111-1111", 200000
+ )
), PayMethod.CREDIT_CARD, 350000
);
- willThrow(MemberNotFoundException.class)
- .given(memberRepository).findById(anyLong());
+ Map> map = new HashMap<>();
+ for (ReservationProductRequestDto reservationProduct : requestDto.reservationProducts()) {
+ map.put(
+ reservationProduct.roomId(),
+ getKeyList(reservationProduct.roomId(), reservationProduct.startDate(), reservationProduct.endDate())
+ );
+ }
//when & then
- assertThatThrownBy(() -> reservationService.saveReservation(memberId, requestDto))
+ assertThatThrownBy(() -> reservationService.saveReservation(memberId, requestDto, map))
.isInstanceOf(MemberNotFoundException.class);
-
- verify(memberRepository, times(1)).findById(anyLong());
- verify(roomRepository, times(0)).findById(anyLong());
- verify(reservationRepository, times(0)).save(any(Reservation.class));
}
@DisplayName("객실 정보가 존재하지 않으면 예약을 저장할 수 없다.")
@Test
void saveReservation_roomNotFound_test() {
//given
- long memberId = 1L;
+ long memberId = member.getId();
long roomId1 = 1L;
- long roomId2 = 2L;
+ long roomId2 = 2000L;
SaveReservationRequestDto requestDto
= new SaveReservationRequestDto(
List.of(
- getReservationProductRequestData(
- roomId1, "2023-11-20", "2023-11-23",
+ new ReservationProductRequestDto(
+ 1L, roomId1, startDate1.toString(), endDate1.toString(),
"visitor1", "010-1111-1111", 150000),
- getReservationProductRequestData(
- roomId2, "2023-11-18", "2023-11-20",
- "visitor2", "010-2222-2222", 200000)
+ new ReservationProductRequestDto(
+ 2L, roomId2, startDate2.toString(), endDate2.toString(),
+ "visitor1", "010-1111-1111", 200000
+ )
), PayMethod.CREDIT_CARD, 350000
);
- given(memberRepository.findById(anyLong()))
- .willReturn(Optional.of(member));
- willThrow(RoomNotFoundException.class)
- .given(roomRepository).findById(anyLong());
+ Map> map = new HashMap<>();
+ for (ReservationProductRequestDto reservationProduct : requestDto.reservationProducts()) {
+ map.put(
+ reservationProduct.roomId(),
+ getKeyList(reservationProduct.roomId(), reservationProduct.startDate(), reservationProduct.endDate())
+ );
+ }
//when & then
- assertThatThrownBy(() -> reservationService.saveReservation(memberId, requestDto))
+ assertThatThrownBy(() -> reservationService.saveReservation(memberId, requestDto, map))
.isInstanceOf(RoomNotFoundException.class);
+ }
- verify(memberRepository, times(1)).findById(anyLong());
- verify(roomRepository, times(1)).findById(anyLong());
- verify(reservationRepository, times(0)).save(any(Reservation.class));
+ private List getKeyList(Long roomId, String startDate, String endDate) {
+ List keyList = new ArrayList<>();
+
+ LocalDate targetDate = DateTimeUtil.toLocalDate(startDate);
+ LocalDate maxDate = DateTimeUtil.toLocalDate(endDate);
+ while (targetDate.isBefore(maxDate)) {
+ keyList.add("roomId:" + roomId + ":" + targetDate);
+ targetDate = targetDate.plusDays(1);
+ }
+
+ return keyList;
}
- private ReservationProductRequestDto getReservationProductRequestData(
- long roomId,
- String startDate,
- String endDate,
- String visitorName,
- String visitorPhone,
- Integer price
- ) {
- String defaultValue = "DEFAULT_VALUE";
- return new ReservationProductRequestDto(
- roomId,
- defaultValue,
- defaultValue,
- 2,
- 4,
- startDate,
- endDate,
- defaultValue,
- defaultValue,
- visitorName,
- visitorPhone,
- price
- );
+ private List getProductTestDataList(int size) {
+ List products = new ArrayList<>();
+
+ for (int i = 1; i <= size; i++) {
+ String productName = "호텔" + i;
+ float starAvg = ThreadLocalRandom.current().nextFloat(0, 5);
+ String infoCenter = String.format("02-1234-%d%d%d%d", i, i, i, i);
+ products.add(
+ productRepository.save(
+ Product.builder()
+ .name(productName)
+ .thumbnail(productName + " 썸네일 url")
+ .description(productName + " 설명")
+ .starAvg(starAvg)
+ .category(Category.TOURIST_HOTEL)
+ .address(
+ Address.builder()
+ .address(productName + " 주소")
+ .detailAddress(productName + " 상세 주소")
+ .mapX(1.0)
+ .mapY(1.5)
+ .build()
+ )
+ .productOption(
+ ProductOption.builder()
+ .cooking(true)
+ .foodPlace("음료 가능")
+ .parking(true)
+ .pickup(false)
+ .infoCenter(infoCenter)
+ .build()
+ )
+ .amenity(
+ Amenity.builder()
+ .barbecue(false)
+ .beauty(true)
+ .beverage(true)
+ .fitness(true)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(true)
+ .publicBath(true)
+ .publicPc(true)
+ .seminar(false)
+ .sports(false)
+ .build()
+ )
+ .build()
+ )
+ );
+ }
+
+ return products;
+ }
+
+ private List getRoomTestDataList(int size, List products) {
+ List rooms = new ArrayList<>();
+ for (int i = 1; i <= size; i++) {
+ String roomName = "객실" + i;
+ rooms.add(
+ roomRepository.save(
+ Room.builder()
+ .code(1000 + i)
+ .product(products.get((i - 1) % products.size()))
+ .name(roomName)
+ .description(roomName + " 설명")
+ .standard(2)
+ .capacity(4)
+ .checkIn(LocalTime.of(14, 0))
+ .checkOut(LocalTime.of(12, 0))
+ .price(
+ RoomPrice.builder()
+ .offWeekDaysMinFee(75000)
+ .offWeekendMinFee(85000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(120000)
+ .build()
+ )
+ .roomOption(
+ RoomOption.builder()
+ .cooking(true)
+ .airCondition(true)
+ .bath(true)
+ .bathFacility(true)
+ .pc(false)
+ .diningTable(true)
+ .hairDryer(true)
+ .homeTheater(false)
+ .internet(true)
+ .cable(false)
+ .refrigerator(true)
+ .sofa(true)
+ .toiletries(true)
+ .tv(true)
+ .build()
+ )
+ .build()
+ )
+ );
+ }
+
+ return rooms;
}
}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/docs/ReservationProductRestControllerDocsTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/docs/ReservationProductRestControllerDocsTest.java
index 7a0a2731..8be980c5 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/docs/ReservationProductRestControllerDocsTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/docs/ReservationProductRestControllerDocsTest.java
@@ -25,7 +25,7 @@ public class ReservationProductRestControllerDocsTest extends RestDocsSupport {
private SecurityUtil securityUtil;
@WithMockUser(roles = "USER")
- @DisplayName("cancel()는 예약 주문 상품을 취소할 수 있다.")
+ @DisplayName("cancel()는 예약 주문 숙소을 취소할 수 있다.")
@Test
void cancel() throws Exception {
// given
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/unit/controller/ReservationProductRestControllerTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/unit/controller/ReservationProductRestControllerTest.java
index bdaf7955..62636dca 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/unit/controller/ReservationProductRestControllerTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/unit/controller/ReservationProductRestControllerTest.java
@@ -45,7 +45,7 @@ void setUp(@Autowired WebApplicationContext applicationContext) {
}
@WithMockUser(roles = "USER")
- @DisplayName("[api][DELETE][정상] 예약 주문 상품 취소 API 테스트")
+ @DisplayName("[api][DELETE][정상] 예약 주문 숙소 취소 API 테스트")
@Test
void saveReservation_Api_test() throws Exception {
//given
diff --git a/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/unit/service/ReservationProductServiceTest.java b/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/unit/service/ReservationProductServiceTest.java
index 04b86452..08893985 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/unit/service/ReservationProductServiceTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/reservationproduct/unit/service/ReservationProductServiceTest.java
@@ -36,7 +36,7 @@ class ReservationProductServiceTest {
@Mock
private ReservationProductRepository reservationProductRepository;
- @DisplayName("예약 상품을 취소할 수 있다.")
+ @DisplayName("예약 숙소을 취소할 수 있다.")
@Test
void cancel_test() {
//given
@@ -79,7 +79,7 @@ void cancel_test() {
verify(reservationProductRepository, times(1)).findByIdWithReservation(anyLong());
}
- @DisplayName("예약 상품 정보가 존재하지 않으면, 예약 상품을 취소할 수 있다.")
+ @DisplayName("예약 숙소 정보가 존재하지 않으면, 예약 숙소을 취소할 수 있다.")
@Test
void cancel_reservationProductNotFound_test() {
//given
@@ -96,7 +96,7 @@ void cancel_reservationProductNotFound_test() {
verify(reservationProductRepository, times(1)).findByIdWithReservation(anyLong());
}
- @DisplayName("예약 상품 주문자 정보와 현재 인증 객체 정보가 일치하지 않으며, 예약 상품을 취소할 수 없다.")
+ @DisplayName("예약 숙소 주문자 정보와 현재 인증 객체 정보가 일치하지 않으며, 예약 숙소을 취소할 수 없다.")
@Test
void cancel_forbidden_test() {
//given
diff --git a/src/test/java/com/fc/shimpyo_be/domain/room/docs/RoomRestControllerDocsTest.java b/src/test/java/com/fc/shimpyo_be/domain/room/docs/RoomRestControllerDocsTest.java
new file mode 100644
index 00000000..74ac6c7a
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/domain/room/docs/RoomRestControllerDocsTest.java
@@ -0,0 +1,126 @@
+package com.fc.shimpyo_be.domain.room.docs;
+
+import com.fc.shimpyo_be.config.RestDocsSupport;
+import com.fc.shimpyo_be.domain.room.dto.response.RoomWithProductResponseDto;
+import com.fc.shimpyo_be.domain.room.service.RoomService;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.restdocs.payload.JsonFieldType;
+import org.springframework.security.test.context.support.WithMockUser;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
+import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
+import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
+import static org.springframework.restdocs.snippet.Attributes.key;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+public class RoomRestControllerDocsTest extends RestDocsSupport {
+
+ @MockBean
+ private RoomService roomService;
+
+ @WithMockUser(roles = "USER")
+ @DisplayName("getRoomsWithProductInfo()는 숙소 정보를 포함한 객실 정보 리스트를 조회할 수 있다.")
+ @Test
+ void getRoomsWithProductInfo() throws Exception {
+ //given
+ String requestUrl = "/api/rooms";
+ String roomIdParamList = "1,3,4";
+ List roomIds = List.of(1L, 3L, 4L);
+
+ List rooms = List.of(
+ RoomWithProductResponseDto.builder()
+ .productId(1L)
+ .productName("호텔1")
+ .productThumbnail("호텔1 썸네일")
+ .productAddress("호텔1 주소")
+ .productDetailAddress("호텔1 상세 주소")
+ .roomId(1L)
+ .roomName("객실1")
+ .standard(2)
+ .capacity(4)
+ .checkIn("14:00")
+ .checkOut("12:00")
+ .price(80000L)
+ .build(),
+ RoomWithProductResponseDto.builder()
+ .productId(2L)
+ .productName("호텔2")
+ .productThumbnail("호텔2 썸네일")
+ .productAddress("호텔2 주소")
+ .productDetailAddress("호텔2 상세 주소")
+ .roomId(3L)
+ .roomName("객실3")
+ .standard(2)
+ .capacity(4)
+ .checkIn("14:00")
+ .checkOut("11:30")
+ .price(95000L)
+ .build(),
+ RoomWithProductResponseDto.builder()
+ .productId(3L)
+ .productName("호텔3")
+ .productThumbnail("호텔3 썸네일")
+ .productAddress("호텔3 주소")
+ .productDetailAddress("호텔3 상세 주소")
+ .roomId(4L)
+ .roomName("객실4")
+ .standard(2)
+ .capacity(4)
+ .checkIn("13:00")
+ .checkOut("11:00")
+ .price(80000L)
+ .build()
+ );
+
+ given(roomService.getRoomsWithProductInfo(roomIds))
+ .willReturn(rooms);
+
+ //when & then
+ mockMvc.perform(
+ get(requestUrl)
+ .queryParam("roomIds", roomIdParamList)
+ .characterEncoding(StandardCharsets.UTF_8)
+ )
+ .andExpect(status().isOk())
+ .andDo(restDoc.document(
+ queryParameters(
+ parameterWithName("roomIds").description("숙소/객실 정보를 조회할 객실 식별자 리스트")
+ .attributes(key("constraints").value(
+ List.of(
+ "최소 1개, 최대 3개의 객실 식별자 정보가 필요합니다.",
+ "객실 식별자는 최소 1 이상이어야 합니다."
+ )
+ ))
+ ),
+ responseFields(responseCommon()).and(
+ fieldWithPath("data").type(JsonFieldType.OBJECT).description("응답 데이터"),
+ fieldWithPath("data.rooms").type(JsonFieldType.ARRAY).description("조회한 객실 정보 리스트"),
+ fieldWithPath("data.rooms[].productId").type(JsonFieldType.NUMBER).description("숙소 식별자"),
+ fieldWithPath("data.rooms[].productName").type(JsonFieldType.STRING).description("숙소명"),
+ fieldWithPath("data.rooms[].productThumbnail").type(JsonFieldType.STRING).description("숙소 썸네일 이미지 URL"),
+ fieldWithPath("data.rooms[].productAddress").type(JsonFieldType.STRING).description("숙소 주소"),
+ fieldWithPath("data.rooms[].productDetailAddress").type(JsonFieldType.STRING).description("숙소 상세 주소"),
+ fieldWithPath("data.rooms[].roomId").type(JsonFieldType.NUMBER).description("객실 식별자"),
+ fieldWithPath("data.rooms[].roomName").type(JsonFieldType.STRING).description("객실명"),
+ fieldWithPath("data.rooms[].standard").type(JsonFieldType.NUMBER).description("기준 인원"),
+ fieldWithPath("data.rooms[].capacity").type(JsonFieldType.NUMBER).description("최대 인원"),
+ fieldWithPath("data.rooms[].checkIn").type(JsonFieldType.STRING).description("체크인 시간"),
+ fieldWithPath("data.rooms[].checkOut").type(JsonFieldType.STRING).description("체크아웃 시간"),
+ fieldWithPath("data.rooms[].price").type(JsonFieldType.NUMBER).description("객실 가격")
+ )
+ )
+ );
+
+ verify(roomService, times(1)).getRoomsWithProductInfo(roomIds);
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/room/unit/controller/RoomRestControllerTest.java b/src/test/java/com/fc/shimpyo_be/domain/room/unit/controller/RoomRestControllerTest.java
new file mode 100644
index 00000000..8649321a
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/domain/room/unit/controller/RoomRestControllerTest.java
@@ -0,0 +1,230 @@
+package com.fc.shimpyo_be.domain.room.unit.controller;
+
+import com.fc.shimpyo_be.config.AbstractContainersSupport;
+import com.fc.shimpyo_be.domain.room.dto.response.RoomWithProductResponseDto;
+import com.fc.shimpyo_be.domain.room.service.RoomService;
+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.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import static org.hamcrest.Matchers.*;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+public class RoomRestControllerTest extends AbstractContainersSupport {
+
+ private MockMvc mockMvc;
+
+ @MockBean
+ private RoomService roomService;
+
+ @BeforeEach
+ void setUp(@Autowired WebApplicationContext applicationContext) {
+ this.mockMvc = MockMvcBuilders
+ .webAppContextSetup(applicationContext)
+ .apply(springSecurity())
+ .alwaysDo(print())
+ .build();
+ }
+
+ @WithMockUser(roles = "USER")
+ @DisplayName("[api][GET][정상] 숙소 정보 포함 객실 정보 리스트 조회 API 테스트 - 콤마 리스트 파라메터")
+ @Test
+ void getRoomsWithProductInfo_api_test() throws Exception {
+ //given
+ String requestUrl = "/api/rooms";
+ String roomIdParamList = "1, 2, 3";
+ List roomIds = List.of(1L, 2L, 3L);
+
+ List rooms = List.of(
+ RoomWithProductResponseDto.builder()
+ .productId(1L)
+ .productName("호텔1")
+ .productThumbnail("호텔1 썸네일")
+ .productAddress("호텔1 주소")
+ .productDetailAddress("호텔1 상세 주소")
+ .roomId(1L)
+ .roomName("객실1")
+ .standard(2)
+ .capacity(4)
+ .checkIn("14:00")
+ .checkOut("12:00")
+ .price(80000L)
+ .build(),
+ RoomWithProductResponseDto.builder()
+ .productId(2L)
+ .productName("호텔2")
+ .productThumbnail("호텔2 썸네일")
+ .productAddress("호텔2 주소")
+ .productDetailAddress("호텔2 상세 주소")
+ .roomId(3L)
+ .roomName("객실3")
+ .standard(2)
+ .capacity(4)
+ .checkIn("14:00")
+ .checkOut("11:30")
+ .price(95000L)
+ .build(),
+ RoomWithProductResponseDto.builder()
+ .productId(3L)
+ .productName("호텔3")
+ .productThumbnail("호텔3 썸네일")
+ .productAddress("호텔3 주소")
+ .productDetailAddress("호텔3 상세 주소")
+ .roomId(4L)
+ .roomName("객실4")
+ .standard(2)
+ .capacity(4)
+ .checkIn("13:00")
+ .checkOut("11:00")
+ .price(95000L)
+ .build()
+ );
+
+ given(roomService.getRoomsWithProductInfo(roomIds))
+ .willReturn(rooms);
+
+ //when & then
+ mockMvc.perform(
+ get(requestUrl)
+ .param("roomIds", roomIdParamList)
+ .characterEncoding(StandardCharsets.UTF_8)
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code", is(200)))
+ .andExpect(jsonPath("$.data").isNotEmpty());
+
+ verify(roomService, times(1)).getRoomsWithProductInfo(roomIds);
+ }
+
+ @WithMockUser(roles = "USER")
+ @DisplayName("[api][GET][정상] 숙소 정보 포함 객실 정보 리스트 조회 API 테스트 - 파라메터 개별로 매핑")
+ @Test
+ void getRoomsWithProductInfo_api_request_params_test() throws Exception {
+ //given
+ String requestUrl = "/api/rooms";
+ List roomIds = List.of(1L, 2L, 3L);
+
+ List rooms = List.of(
+ RoomWithProductResponseDto.builder()
+ .productId(1L)
+ .productName("호텔1")
+ .productThumbnail("호텔1 썸네일")
+ .productAddress("호텔1 주소")
+ .productDetailAddress("호텔1 상세 주소")
+ .roomId(1L)
+ .roomName("객실1")
+ .standard(2)
+ .capacity(4)
+ .checkIn("14:00")
+ .checkOut("12:00")
+ .price(80000L)
+ .build(),
+ RoomWithProductResponseDto.builder()
+ .productId(2L)
+ .productName("호텔2")
+ .productThumbnail("호텔2 썸네일")
+ .productAddress("호텔2 주소")
+ .productDetailAddress("호텔2 상세 주소")
+ .roomId(3L)
+ .roomName("객실3")
+ .standard(2)
+ .capacity(4)
+ .checkIn("14:00")
+ .checkOut("11:30")
+ .price(95000L)
+ .build(),
+ RoomWithProductResponseDto.builder()
+ .productId(3L)
+ .productName("호텔3")
+ .productThumbnail("호텔3 썸네일")
+ .productAddress("호텔3 주소")
+ .productDetailAddress("호텔3 상세 주소")
+ .roomId(4L)
+ .roomName("객실4")
+ .standard(2)
+ .capacity(4)
+ .checkIn("13:00")
+ .checkOut("11:00")
+ .price(95000L)
+ .build()
+ );
+
+ given(roomService.getRoomsWithProductInfo(roomIds))
+ .willReturn(rooms);
+
+ //when & then
+ mockMvc.perform(
+ get(requestUrl)
+ .param("roomIds", String.valueOf(roomIds.get(0)))
+ .param("roomIds", String.valueOf(roomIds.get(1)))
+ .param("roomIds", String.valueOf(roomIds.get(2)))
+ .characterEncoding(StandardCharsets.UTF_8)
+ )
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.code", is(200)))
+ .andExpect(jsonPath("$.data").isNotEmpty());
+
+ verify(roomService, times(1)).getRoomsWithProductInfo(roomIds);
+ }
+
+ @WithMockUser(roles = "USER")
+ @DisplayName("[api][GET][실패] 숙소 정보 포함 객실 정보 리스트 조회 API 테스트 - 요청 데이터 검증 에러(Size)")
+ @Test
+ void getRoomsWithProductInfo_api_size_validation_fail_test() throws Exception {
+ //given
+ String requestUrl = "/api/rooms";
+ String roomIdParamList = "1, 3, 4, 5";
+ List roomIds = List.of(1L, 3L, 4L, 5L);
+
+
+ //when & then
+ mockMvc.perform(
+ get(requestUrl)
+ .param("roomIds", roomIdParamList)
+ .characterEncoding(StandardCharsets.UTF_8)
+ )
+ .andExpect(status().is4xxClientError())
+ .andExpect(jsonPath("$.code", is(400)));
+
+ verify(roomService, times(0)).getRoomsWithProductInfo(roomIds);
+ }
+
+ @WithMockUser(roles = "USER")
+ @DisplayName("[api][GET][실패] 숙소 정보 포함 객실 정보 리스트 조회 API 테스트 - 요청 데이터 검증 에러(Min)")
+ @Test
+ void getRoomsWithProductInfo_api_min_validation_fail_test() throws Exception {
+ //given
+ String requestUrl = "/api/rooms";
+ String roomIdParamList = "-2, 3";
+
+ //when & then
+ mockMvc.perform(
+ get(requestUrl)
+ .param("roomIds", roomIdParamList)
+ .characterEncoding(StandardCharsets.UTF_8)
+ )
+ .andExpect(status().is4xxClientError())
+ .andExpect(jsonPath("$.code", is(400)));
+
+ verify(roomService, times(0)).getRoomsWithProductInfo(anyList());
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/room/unit/repository/RoomRepositoryTest.java b/src/test/java/com/fc/shimpyo_be/domain/room/unit/repository/RoomRepositoryTest.java
new file mode 100644
index 00000000..7956401c
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/domain/room/unit/repository/RoomRepositoryTest.java
@@ -0,0 +1,153 @@
+package com.fc.shimpyo_be.domain.room.unit.repository;
+
+import com.fc.shimpyo_be.config.DatabaseCleanUp;
+import com.fc.shimpyo_be.config.TestDBCleanerConfig;
+import com.fc.shimpyo_be.config.TestQuerydslConfig;
+import com.fc.shimpyo_be.domain.product.entity.*;
+import com.fc.shimpyo_be.domain.product.repository.ProductRepository;
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
+import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
+import lombok.extern.slf4j.Slf4j;
+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 java.time.LocalTime;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Slf4j
+@Import({TestQuerydslConfig.class, TestDBCleanerConfig.class})
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+@DataJpaTest
+public class RoomRepositoryTest {
+
+ @Autowired
+ private RoomRepository roomRepository;
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ @Autowired
+ private DatabaseCleanUp databaseCleanUp;
+
+ private final String[] tableNameArray = {
+ "product", "room", "product_option", "address", "room_option", "amenity", "room_price"
+ };
+
+ private List roomIds;
+
+ @BeforeEach
+ void setUp() {
+ databaseCleanUp.cleanUp(tableNameArray);
+
+ Product product = productRepository.save(
+ Product.builder()
+ .name("호텔")
+ .thumbnail("호텔 썸네일 url")
+ .description("호텔 설명")
+ .starAvg(4.2f)
+ .category(Category.TOURIST_HOTEL)
+ .address(
+ Address.builder()
+ .address("호텔 주소")
+ .detailAddress("호텔 상세 주소")
+ .mapX(1.0)
+ .mapY(1.5)
+ .build()
+ )
+ .productOption(
+ ProductOption.builder()
+ .cooking(true)
+ .foodPlace("음료 가능")
+ .parking(true)
+ .pickup(false)
+ .infoCenter("1500-0000")
+ .build()
+ )
+ .amenity(
+ Amenity.builder()
+ .barbecue(false)
+ .beauty(true)
+ .beverage(true)
+ .fitness(true)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(true)
+ .publicBath(true)
+ .publicPc(true)
+ .seminar(false)
+ .sports(false)
+ .build()
+ )
+ .build()
+ );
+
+ roomIds = new LinkedList<>();
+
+ for (int i = 1; i <= 5; i++) {
+ String roomName = "호텔 객실" + i;
+ roomIds.add(
+ roomRepository.save(
+ Room.builder()
+ .product(product)
+ .name(roomName)
+ .description(roomName + " 설명")
+ .standard(2)
+ .capacity(4)
+ .checkIn(LocalTime.of(14, 0))
+ .checkOut(LocalTime.of(12, 0))
+ .price(
+ RoomPrice.builder()
+ .offWeekDaysMinFee(75000)
+ .offWeekendMinFee(85000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(120000)
+ .build()
+ )
+ .roomOption(
+ RoomOption.builder()
+ .cooking(true)
+ .airCondition(true)
+ .bath(true)
+ .bathFacility(true)
+ .pc(false)
+ .diningTable(true)
+ .hairDryer(true)
+ .homeTheater(false)
+ .internet(true)
+ .cable(false)
+ .refrigerator(true)
+ .sofa(true)
+ .toiletries(true)
+ .tv(true)
+ .build()
+ )
+ .build()
+ ).getId()
+ );
+ }
+ }
+
+ @DisplayName("findAllInIdsWithProductAndPrice 테스트")
+ @Test
+ void findAllInIdsWithProductAndPrice() {
+ //given
+
+ //when
+ List result = roomRepository.findAllInIdsWithProductAndPrice(roomIds);
+
+ //then
+ assertThat(result).hasSize(5);
+ assertThat(result.get(0).getProduct().getId()).isEqualTo(1);
+ assertThat(result.get(0).getPrice()).isNotNull();
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/room/unit/service/RoomServiceTest.java b/src/test/java/com/fc/shimpyo_be/domain/room/unit/service/RoomServiceTest.java
new file mode 100644
index 00000000..0bd36745
--- /dev/null
+++ b/src/test/java/com/fc/shimpyo_be/domain/room/unit/service/RoomServiceTest.java
@@ -0,0 +1,134 @@
+package com.fc.shimpyo_be.domain.room.unit.service;
+
+import com.fc.shimpyo_be.domain.product.entity.*;
+import com.fc.shimpyo_be.domain.room.dto.response.RoomWithProductResponseDto;
+import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomOption;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
+import com.fc.shimpyo_be.domain.room.repository.RoomRepository;
+import com.fc.shimpyo_be.domain.room.service.RoomService;
+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 java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+public class RoomServiceTest {
+
+ @InjectMocks
+ private RoomService roomService;
+
+ @Mock
+ private RoomRepository roomRepository;
+
+ @DisplayName("객실 식별자 리스트에 해당하는 객실과 숙소 정보를 리스트로 반환한다.")
+ @Test
+ void getRoomsWithProductInfo_test() {
+ //given
+ List roomIds = List.of(1L, 3L, 4L);
+
+ Product product = Product.builder()
+ .name("호텔")
+ .thumbnail("호텔 썸네일 url")
+ .description("호텔 설명")
+ .starAvg(4.2f)
+ .category(Category.TOURIST_HOTEL)
+ .address(
+ Address.builder()
+ .address("호텔 주소")
+ .detailAddress("호텔 상세 주소")
+ .mapX(1.0)
+ .mapY(1.5)
+ .build()
+ )
+ .productOption(
+ ProductOption.builder()
+ .cooking(true)
+ .foodPlace("음료 가능")
+ .parking(true)
+ .pickup(false)
+ .infoCenter("1500-0000")
+ .build()
+ )
+ .amenity(
+ Amenity.builder()
+ .barbecue(false)
+ .beauty(true)
+ .beverage(true)
+ .fitness(true)
+ .bicycle(false)
+ .campfire(false)
+ .karaoke(true)
+ .publicBath(true)
+ .publicPc(true)
+ .seminar(false)
+ .sports(false)
+ .build()
+ )
+ .build();
+
+ List rooms = new ArrayList<>();
+
+ for (int i = 1; i <= 3; i++) {
+ String roomName = "호텔 객실" + i;
+ rooms.add(
+ Room.builder()
+ .product(product)
+ .name(roomName)
+ .description(roomName + " 설명")
+ .standard(2)
+ .capacity(4)
+ .checkIn(LocalTime.of(14, 0))
+ .checkOut(LocalTime.of(12, 0))
+ .price(
+ RoomPrice.builder()
+ .offWeekDaysMinFee(75000)
+ .offWeekendMinFee(85000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(120000)
+ .build()
+ )
+ .roomOption(
+ RoomOption.builder()
+ .cooking(true)
+ .airCondition(true)
+ .bath(true)
+ .bathFacility(true)
+ .pc(false)
+ .diningTable(true)
+ .hairDryer(true)
+ .homeTheater(false)
+ .internet(true)
+ .cable(false)
+ .refrigerator(true)
+ .sofa(true)
+ .toiletries(true)
+ .tv(true)
+ .build()
+ )
+ .build()
+ );
+ }
+
+ given(roomRepository.findAllInIdsWithProductAndPrice(roomIds)).willReturn(rooms);
+
+ //when
+ List result = roomService.getRoomsWithProductInfo(roomIds);
+
+ //then
+ assertThat(result).hasSize(3);
+
+ verify(roomRepository, times(1)).findAllInIdsWithProductAndPrice(roomIds);
+ }
+}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/star/docs/StarRestControllerDocsTest.java b/src/test/java/com/fc/shimpyo_be/domain/star/docs/StarRestControllerDocsTest.java
index 29179519..eac8188b 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/star/docs/StarRestControllerDocsTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/star/docs/StarRestControllerDocsTest.java
@@ -60,7 +60,7 @@ void register() throws Exception {
requestFields(
fieldWithPath("reservationProductId").type(JsonFieldType.NUMBER).description("별점 등록 대상 예약 상품 식별자")
.attributes(key("constraints").value(
- starRegisterDescriptions.descriptionsForProperty("productId"))),
+ starRegisterDescriptions.descriptionsForProperty("reservationProductId"))),
fieldWithPath("productId").type(JsonFieldType.NUMBER).description("별점 등록 대상 숙소 식별자")
.attributes(key("constraints").value(
starRegisterDescriptions.descriptionsForProperty("productId"))),
diff --git a/src/test/java/com/fc/shimpyo_be/domain/star/unit/controller/StarRestControllerTest.java b/src/test/java/com/fc/shimpyo_be/domain/star/unit/controller/StarRestControllerTest.java
index 66050359..8484dd03 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/star/unit/controller/StarRestControllerTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/star/unit/controller/StarRestControllerTest.java
@@ -4,6 +4,8 @@
import com.fc.shimpyo_be.config.AbstractContainersSupport;
import com.fc.shimpyo_be.domain.star.dto.request.StarRegisterRequestDto;
import com.fc.shimpyo_be.domain.star.dto.response.StarResponseDto;
+import com.fc.shimpyo_be.domain.star.exception.CannotBeforeCheckOutException;
+import com.fc.shimpyo_be.domain.star.exception.ExpiredRegisterDateException;
import com.fc.shimpyo_be.domain.star.service.StarService;
import com.fc.shimpyo_be.global.util.SecurityUtil;
import org.junit.jupiter.api.BeforeEach;
@@ -18,6 +20,7 @@
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
+import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
@@ -50,7 +53,7 @@ void setUp(@Autowired WebApplicationContext applicationContext) {
}
@WithMockUser(roles = "USER")
- @DisplayName("[api][POST] 별점 등록 API 성공 테스트")
+ @DisplayName("[api][POST][정상] 별점 등록 API 테스트")
@Test
void register_success_test() throws Exception {
// given
@@ -77,4 +80,58 @@ void register_success_test() throws Exception {
.andExpect(jsonPath("$.data.starId").isNumber())
.andExpect(jsonPath("$.data.score").isNotEmpty());
}
+
+ @WithMockUser(roles = "USER")
+ @DisplayName("[api][POST][에러] 별점 등록 API 테스트 - 체크아웃 이전 등록 기간 검증 에러")
+ @Test
+ void register_cannotBeforeCheckOutException_test() throws Exception {
+ // given
+ String requestUrl = "/api/stars";
+ Long productId = 1L;
+ float score = 3.5F;
+
+ StarRegisterRequestDto requestDto
+ = new StarRegisterRequestDto(1L, productId, score);
+
+ given(securityUtil.getCurrentMemberId()).willReturn(1L);
+ given(starService.register(anyLong(), any(StarRegisterRequestDto.class)))
+ .willThrow(new CannotBeforeCheckOutException());
+
+ // when & then
+ mockMvc.perform(
+ post(requestUrl)
+ .content(objectMapper.writeValueAsString(requestDto))
+ .contentType(MediaType.APPLICATION_JSON)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code", is(400)))
+ .andExpect(jsonPath("$.data").isEmpty());
+ }
+
+ @WithMockUser(roles = "USER")
+ @DisplayName("[api][POST][에러] 별점 등록 API 테스트 - 만료된 등록 기간 검증 에러")
+ @Test
+ void register_expiredRegisterDateException_test() throws Exception {
+ // given
+ String requestUrl = "/api/stars";
+ Long productId = 1L;
+ float score = 3.5F;
+
+ StarRegisterRequestDto requestDto
+ = new StarRegisterRequestDto(1L, productId, score);
+
+ given(securityUtil.getCurrentMemberId()).willReturn(1L);
+ given(starService.register(anyLong(), any(StarRegisterRequestDto.class)))
+ .willThrow(new ExpiredRegisterDateException());
+
+ // when & then
+ mockMvc.perform(
+ post(requestUrl)
+ .content(objectMapper.writeValueAsString(requestDto))
+ .contentType(MediaType.APPLICATION_JSON)
+ )
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.code", is(400)))
+ .andExpect(jsonPath("$.data").isEmpty());
+ }
}
diff --git a/src/test/java/com/fc/shimpyo_be/domain/star/unit/service/StarServiceTest.java b/src/test/java/com/fc/shimpyo_be/domain/star/unit/service/StarServiceTest.java
index b9b9bb82..f49f9aba 100644
--- a/src/test/java/com/fc/shimpyo_be/domain/star/unit/service/StarServiceTest.java
+++ b/src/test/java/com/fc/shimpyo_be/domain/star/unit/service/StarServiceTest.java
@@ -3,7 +3,8 @@
import com.fc.shimpyo_be.domain.member.entity.Authority;
import com.fc.shimpyo_be.domain.member.entity.Member;
import com.fc.shimpyo_be.domain.member.exception.MemberNotFoundException;
-import com.fc.shimpyo_be.domain.member.repository.MemberRepository;
+import com.fc.shimpyo_be.domain.member.service.MemberService;
+import com.fc.shimpyo_be.domain.product.entity.Address;
import com.fc.shimpyo_be.domain.product.entity.Category;
import com.fc.shimpyo_be.domain.product.entity.Product;
import com.fc.shimpyo_be.domain.product.exception.ProductNotFoundException;
@@ -13,10 +14,12 @@
import com.fc.shimpyo_be.domain.reservationproduct.entity.ReservationProduct;
import com.fc.shimpyo_be.domain.reservationproduct.repository.ReservationProductRepository;
import com.fc.shimpyo_be.domain.room.entity.Room;
+import com.fc.shimpyo_be.domain.room.entity.RoomPrice;
import com.fc.shimpyo_be.domain.star.dto.request.StarRegisterRequestDto;
import com.fc.shimpyo_be.domain.star.dto.response.StarResponseDto;
import com.fc.shimpyo_be.domain.star.entity.Star;
import com.fc.shimpyo_be.domain.star.exception.CannotBeforeCheckOutException;
+import com.fc.shimpyo_be.domain.star.exception.ExpiredRegisterDateException;
import com.fc.shimpyo_be.domain.star.repository.StarRepository;
import com.fc.shimpyo_be.domain.star.service.StarService;
import org.junit.jupiter.api.BeforeEach;
@@ -31,7 +34,8 @@
import java.time.LocalTime;
import java.util.Optional;
-import static org.assertj.core.api.Assertions.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
@@ -49,7 +53,7 @@ public class StarServiceTest {
private StarRepository starRepository;
@Mock
- private MemberRepository memberRepository;
+ private MemberService memberService;
@Mock
private ProductRepository productRepository;
@@ -63,7 +67,9 @@ public class StarServiceTest {
private ReservationProduct reservationProduct;
- private ReservationProduct reservationProduct2;
+ private ReservationProduct reservationProductBeforeCheckOut;
+
+ private ReservationProduct reservationProductExpiredToRegister;
@BeforeEach
void setUp() {
@@ -83,7 +89,12 @@ void setUp() {
.category(Category.TOURIST_HOTEL)
.thumbnail("thumbnail url")
.starAvg(3.5f)
- .address("숙소 주소")
+ .address(Address.builder()
+ .address("숙소 주소")
+ .detailAddress("숙소 상세 주소")
+ .mapX(1.0)
+ .mapY(1.0)
+ .build())
.build();
Reservation reservation = Reservation.builder()
@@ -96,26 +107,54 @@ void setUp() {
.id(2L)
.reservation(reservation)
.room(Room.builder()
- .price(50000)
+ .price(RoomPrice.builder()
+ .offWeekDaysMinFee(50000)
+ .offWeekendMinFee(60000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(110000)
+ .build())
.description("객실정보")
.product(product)
.checkOut(LocalTime.of(12, 0))
.build()
)
- .endDate(LocalDate.of(2023, 11, 29))
+ .endDate(LocalDate.now().minusDays(7))
.build();
- reservationProduct2 = ReservationProduct.builder()
+ reservationProductBeforeCheckOut = ReservationProduct.builder()
.id(3L)
.reservation(reservation)
.room(Room.builder()
- .price(50000)
+ .price(RoomPrice.builder()
+ .offWeekDaysMinFee(50000)
+ .offWeekendMinFee(60000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(110000)
+ .build())
.description("객실정보")
.product(product)
.checkOut(LocalTime.of(12, 0))
.build()
)
- .endDate(LocalDate.of(2100, 11, 29))
+ .endDate(LocalDate.now().plusDays(5))
+ .build();
+
+ reservationProductExpiredToRegister = ReservationProduct.builder()
+ .id(4L)
+ .reservation(reservation)
+ .room(Room.builder()
+ .price(RoomPrice.builder()
+ .offWeekDaysMinFee(50000)
+ .offWeekendMinFee(60000)
+ .peakWeekDaysMinFee(100000)
+ .peakWeekendMinFee(110000)
+ .build())
+ .description("객실정보")
+ .product(product)
+ .checkOut(LocalTime.of(12, 0))
+ .build()
+ )
+ .endDate(LocalDate.now().minusDays(15))
.build();
}
@@ -127,8 +166,8 @@ void register_test() {
StarRegisterRequestDto request
= new StarRegisterRequestDto(reservationProduct.getId(), product.getId(), score);
- given(memberRepository.findById(anyLong()))
- .willReturn(Optional.of(member));
+ given(memberService.getMemberById(anyLong()))
+ .willReturn(member);
given(productRepository.findById(anyLong()))
.willReturn(Optional.of(product));
given(reservationProductRepository.findByIdWithRoom(anyLong()))
@@ -150,7 +189,7 @@ void register_test() {
assertThat(response).isNotNull();
assertThat(response.score()).isEqualTo(score);
- verify(memberRepository, times(1)).findById(anyLong());
+ verify(memberService, times(1)).getMemberById(anyLong());
verify(productRepository, times(1)).findById(anyLong());
verify(reservationProductRepository, times(1)).findByIdWithRoom(anyLong());
verify(starRepository, times(1)).save(any(Star.class));
@@ -165,14 +204,14 @@ void register_memberNotFoundException() {
StarRegisterRequestDto request
= new StarRegisterRequestDto(1L, product.getId(), score);
- given(memberRepository.findById(anyLong()))
+ given(memberService.getMemberById(anyLong()))
.willThrow(MemberNotFoundException.class);
// when & then
assertThatThrownBy(() -> starService.register(memberId, request))
.isInstanceOf(MemberNotFoundException.class);
- verify(memberRepository, times(1)).findById(anyLong());
+ verify(memberService, times(1)).getMemberById(anyLong());
verify(productRepository, times(0)).findById(anyLong());
verify(starRepository, times(0)).save(any(Star.class));
}
@@ -187,8 +226,8 @@ void register_productNotFoundException() {
StarRegisterRequestDto request
= new StarRegisterRequestDto(1L, productId, score);
- given(memberRepository.findById(anyLong()))
- .willReturn(Optional.ofNullable(member));
+ given(memberService.getMemberById(anyLong()))
+ .willReturn(member);
given(reservationProductRepository.findByIdWithRoom(anyLong()))
.willReturn(Optional.ofNullable(reservationProduct));
given(productRepository.findById(anyLong()))
@@ -198,7 +237,7 @@ void register_productNotFoundException() {
assertThatThrownBy(() -> starService.register(memberId, request))
.isInstanceOf(ProductNotFoundException.class);
- verify(memberRepository, times(1)).findById(anyLong());
+ verify(memberService, times(1)).getMemberById(anyLong());
verify(reservationProductRepository, times(1)).findByIdWithRoom(anyLong());
verify(productRepository, times(1)).findById(anyLong());
verify(starRepository, times(0)).save(any(Star.class));
@@ -212,18 +251,44 @@ void register_cannotBeforeCheckOutException() {
long productId = 1000L;
float score = 4F;
StarRegisterRequestDto request
- = new StarRegisterRequestDto(1L, productId, score);
+ = new StarRegisterRequestDto(reservationProductBeforeCheckOut.getId(), productId, score);
- given(memberRepository.findById(anyLong()))
- .willReturn(Optional.ofNullable(member));
+ given(memberService.getMemberById(anyLong()))
+ .willReturn(member);
given(reservationProductRepository.findByIdWithRoom(anyLong()))
- .willReturn(Optional.ofNullable(reservationProduct2));
+ .willReturn(Optional.ofNullable(reservationProductBeforeCheckOut));
// when & then
assertThatThrownBy(() -> starService.register(memberId, request))
.isInstanceOf(CannotBeforeCheckOutException.class);
- verify(memberRepository, times(1)).findById(anyLong());
+ verify(memberService, times(1)).getMemberById(anyLong());
+ verify(reservationProductRepository, times(1)).findByIdWithRoom(anyLong());
+ verify(productRepository, times(0)).findById(anyLong());
+ verify(starRepository, times(0)).save(any(Star.class));
+ }
+
+ @DisplayName("별점 등록일시가 체크아웃 일시 2주 이후일 경우 등록할 수 없다.")
+ @Test
+ void register_expiredRegisterDateException() {
+ // given
+ long memberId = member.getId();
+ long productId = 1000L;
+ float score = 4F;
+
+ StarRegisterRequestDto request
+ = new StarRegisterRequestDto(reservationProductExpiredToRegister.getId(), productId, score);
+
+ given(memberService.getMemberById(anyLong()))
+ .willReturn(member);
+ given(reservationProductRepository.findByIdWithRoom(anyLong()))
+ .willReturn(Optional.ofNullable(reservationProductExpiredToRegister));
+
+ // when & then
+ assertThatThrownBy(() -> starService.register(memberId, request))
+ .isInstanceOf(ExpiredRegisterDateException.class);
+
+ verify(memberService, times(1)).getMemberById(anyLong());
verify(reservationProductRepository, times(1)).findByIdWithRoom(anyLong());
verify(productRepository, times(0)).findById(anyLong());
verify(starRepository, times(0)).save(any(Star.class));
diff --git a/src/test/resources/testdata/reservation-repository-setup.sql b/src/test/resources/testdata/reservation-repository-setup.sql
deleted file mode 100644
index 5d9e17d0..00000000
--- a/src/test/resources/testdata/reservation-repository-setup.sql
+++ /dev/null
@@ -1,32 +0,0 @@
-insert into member (id, name, email, password, photo_url, authority)
-values (1, 'member', 'member@email.com', 'password', 'photoUrl', 'ROLE_USER');
-
-insert into product (id, name, category, description, star_avg, thumbnail, address)
-values (1, '호텔1', 'HOTEL', '호텔1 설명', 4.1, '호텔1 사진 url', '서울시 강남구 한남동');
-
-insert into product (id, name, category, description, star_avg, thumbnail, address)
-values (2, '호텔2', 'HOTEL', '호텔2 설명', 3.5, '호텔2 사진 url', '서울시 송파구 잠실동');
-
-insert into room (id, name, product_id, description, standard, capacity, price, check_in, check_out)
-values (1, '객실1', 1, '객실1 설명', 2, 4, 95000, '13:00:00', '12:00:00');
-
-insert into room (id, name, product_id, description, standard, capacity, price, check_in, check_out)
-values (2, '객실2', 1, '객실2 설명', 2, 4, 105000, '14:00:00', '12:30:00');
-
-insert into room (id, name, product_id, description, standard, capacity, price, check_in, check_out)
-values (3, '객실3', 2, '객실3 설명', 3, 5, 125000, '13:00:00', '12:00:00');
-
-insert into reservation (id, member_id, pay_method, total_price)
-values (1, 1, 'CREDIT_CARD', 240000);
-
-insert into reservation (id, member_id, pay_method, total_price)
-values (2, 1, 'CREDIT_CARD', 240000);
-
-insert into reservation_product (id, reservation_id, room_id, start_date, end_date, price)
-values (1, 1, 1, '2023-12-10', '2023-12-13', 95000);
-
-insert into reservation_product (id, reservation_id, room_id, start_date, end_date, price)
-values (2, 1, 3, '2023-12-13', '2023-12-15', 125000);
-
-insert into reservation_product (id, reservation_id, room_id, start_date, end_date, price)
-values (3, 2, 2, '2023-12-24', '2023-12-27', 105000);
diff --git a/src/test/resources/testdata/reservation-service-insert.sql b/src/test/resources/testdata/reservation-service-insert.sql
deleted file mode 100644
index 64ba31bc..00000000
--- a/src/test/resources/testdata/reservation-service-insert.sql
+++ /dev/null
@@ -1,14 +0,0 @@
-insert into reservation (id, member_id, pay_method, total_price)
-values (1, 1, 'CREDIT_CARD', 240000);
-
-insert into reservation (id, member_id, pay_method, total_price)
-values (2, 1, 'CREDIT_CARD', 240000);
-
-insert into reservation_product (id, reservation_id, room_id, start_date, end_date, price)
-values (1, 1, 1, '2023-12-10', '2023-12-13', 95000);
-
-insert into reservation_product (id, reservation_id, room_id, start_date, end_date, price)
-values (2, 1, 3, '2023-12-13', '2023-12-15', 125000);
-
-insert into reservation_product (id, reservation_id, room_id, start_date, end_date, price)
-values (3, 2, 2, '2023-12-24', '2023-12-27', 105000);
diff --git a/src/test/resources/testdata/reservation-service-setup.sql b/src/test/resources/testdata/reservation-service-setup.sql
deleted file mode 100644
index 47d69658..00000000
--- a/src/test/resources/testdata/reservation-service-setup.sql
+++ /dev/null
@@ -1,17 +0,0 @@
-insert into member (id, name, email, password, photo_url, authority)
-values (1, 'member', 'member@email.com', 'password', 'photoUrl', 'ROLE_USER');
-
-insert into product (id, name, category, description, star_avg, thumbnail, address)
-values (1, '호텔1', 'HOTEL', '호텔1 설명', 4.1, '호텔1 사진 url', '서울시 강남구 한남동');
-
-insert into product (id, name, category, description, star_avg, thumbnail, address)
-values (2, '호텔2', 'HOTEL', '호텔2 설명', 3.5, '호텔2 사진 url', '서울시 송파구 잠실동');
-
-insert into room (id, name, product_id, description, standard, capacity, price, check_in, check_out)
-values (1, '객실1', 1, '객실1 설명', 2, 4, 95000, '13:00:00', '12:00:00');
-
-insert into room (id, name, product_id, description, standard, capacity, price, check_in, check_out)
-values (2, '객실2', 1, '객실2 설명', 2, 4, 105000, '14:00:00', '12:30:00');
-
-insert into room (id, name, product_id, description, standard, capacity, price, check_in, check_out)
-values (3, '객실3', 2, '객실3 설명', 3, 5, 125000, '13:00:00', '12:00:00');