From 0b0ebe0d90b945eb00a7da081c8b07d5100f330e Mon Sep 17 00:00:00 2001 From: "Sunguk Yang (Kelly)" Date: Thu, 18 Apr 2024 17:20:32 +0900 Subject: [PATCH 01/15] =?UTF-8?q?[1=20-=203=EB=8B=A8=EA=B3=84=20=EB=B0=A9?= =?UTF-8?q?=ED=83=88=EC=B6=9C=20=EC=98=88=EC=95=BD=20=EA=B4=80=EB=A6=AC]?= =?UTF-8?q?=20=EC=BC=88=EB=A6=AC(=EC=96=91=EC=84=B1=EC=9A=B1)=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=A0=9C=EC=B6=9C=ED=95=A9=EB=8B=88=EB=8B=A4.=20(#?= =?UTF-8?q?71)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Welcome Page 생성 * feat: /admin/index Page 수정 및 라우팅 설정 /admin/index Page의 Bootstrap 의존성을 제거함. /admin/index의 GET 라우팅을 설정함. * feat: 예약 데이터 조회 기능 구현 * feat: 예약 추가 및 삭제 기능 구현 * refactor: html 경로 정정 * refactor: repository 클래스 리팩터링 * refactor: controller 클래스 리팩터링 * docs: README.md 추가 * refactor: ReservationEntity -> Reservation 변경 * refactor: Reservation 불필요한 정적 팩터리 메서드를 생성자로 변경 * refactor: ReservationRepository 불필요한 final 키워드 제거 * refactor: SaveReservationRequest 메서드명 변경 SaveReservationRequest의 toEntity 메서드명을 toReservation으로 변경함 * refactor: AdminApiController ResponseEntity 제거 * refactor: ReservationDto -> ReservationResponse 클래스명 변경 * test: Reservation 클래스 테스트 로직 작성 * test: MemoryReservationRepository 테스트 코드 작성 * test: SaveReservationRequest 테스트 로직 작성 * refactor: MemoryReservationRepository 자료구조와 인덱스값을 멤버 변수로 변경 * test: MemoryReservationRepositoryTest 정정 * test: MemoryReservationRepositoryTest 불필요한 어노테이션 제거 * test: AdminApiControllerTest 테스트 단위 분리 * test: ReservationResponse 테스트 로직 작성 --------- Co-authored-by: MingyeomKim --- docs/README.md | 12 +++ .../controller/AdminApiController.java | 46 ++++++++++++ .../controller/AdminWebController.java | 18 +++++ .../java/roomescape/domain/ClientName.java | 22 ++++++ .../java/roomescape/domain/Reservation.java | 35 +++++++++ .../roomescape/dto/ReservationResponse.java | 17 +++++ .../dto/SaveReservationRequest.java | 17 +++++ .../ReservationExceptionHandler.java | 15 ++++ .../MemoryReservationRepository.java | 48 ++++++++++++ .../repository/ReservationRepository.java | 13 ++++ src/main/resources/templates/admin/index.html | 9 +-- .../templates/admin/reservation-legacy.html | 4 +- .../templates/admin/reservation.html | 4 +- src/main/resources/templates/index.html | 14 ++++ src/test/java/roomescape/MissionStepTest.java | 20 ----- .../controller/AdminApiControllerTest.java | 70 ++++++++++++++++++ .../controller/AdminWebControllerTest.java | 30 ++++++++ .../roomescape/domain/ClientNameTest.java | 22 ++++++ .../roomescape/domain/ReservationTest.java | 27 +++++++ .../dto/ReservationResponseTest.java | 29 ++++++++ .../dto/SaveReservationRequestTest.java | 30 ++++++++ .../MemoryReservationRepositoryTest.java | 74 +++++++++++++++++++ 22 files changed, 547 insertions(+), 29 deletions(-) create mode 100644 docs/README.md create mode 100644 src/main/java/roomescape/controller/AdminApiController.java create mode 100644 src/main/java/roomescape/controller/AdminWebController.java create mode 100644 src/main/java/roomescape/domain/ClientName.java create mode 100644 src/main/java/roomescape/domain/Reservation.java create mode 100644 src/main/java/roomescape/dto/ReservationResponse.java create mode 100644 src/main/java/roomescape/dto/SaveReservationRequest.java create mode 100644 src/main/java/roomescape/exception/ReservationExceptionHandler.java create mode 100644 src/main/java/roomescape/repository/MemoryReservationRepository.java create mode 100644 src/main/java/roomescape/repository/ReservationRepository.java create mode 100644 src/main/resources/templates/index.html delete mode 100644 src/test/java/roomescape/MissionStepTest.java create mode 100644 src/test/java/roomescape/controller/AdminApiControllerTest.java create mode 100644 src/test/java/roomescape/controller/AdminWebControllerTest.java create mode 100644 src/test/java/roomescape/domain/ClientNameTest.java create mode 100644 src/test/java/roomescape/domain/ReservationTest.java create mode 100644 src/test/java/roomescape/dto/ReservationResponseTest.java create mode 100644 src/test/java/roomescape/dto/SaveReservationRequestTest.java create mode 100644 src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..a78eb1edd --- /dev/null +++ b/docs/README.md @@ -0,0 +1,12 @@ +# 웹 페이지 조회 + +- [X] 어드민 페이지 조회 +- [X] 예약 페이지 조회 + +# API + +- [X] 예약 정보 전체 조회 +- [X] 예약 정보 저장 + - [X] 예약자 이름 검증 + - 예약자 이름은 `1`이상 `5`이하 문자열만 허용 +- [X] 예약 정보 삭제 diff --git a/src/main/java/roomescape/controller/AdminApiController.java b/src/main/java/roomescape/controller/AdminApiController.java new file mode 100644 index 000000000..a4f94e888 --- /dev/null +++ b/src/main/java/roomescape/controller/AdminApiController.java @@ -0,0 +1,46 @@ +package roomescape.controller; + +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import roomescape.domain.Reservation; +import roomescape.dto.ReservationResponse; +import roomescape.dto.SaveReservationRequest; +import roomescape.repository.ReservationRepository; + +import java.util.List; + +@RestController +public class AdminApiController { + private final ReservationRepository reservationRepository; + + public AdminApiController(final ReservationRepository reservationRepository) { + this.reservationRepository = reservationRepository; + } + + @GetMapping("/reservations") + public List getReservations() { + List reservations = reservationRepository.findAll() + .stream() + .map(ReservationResponse::from) + .toList(); + + return reservations; + } + + @PostMapping("/reservations") + public ReservationResponse saveReservation(@RequestBody final SaveReservationRequest request) { + Reservation reservation = request.toReservation(); + Reservation savedReservation = reservationRepository.save(reservation); + + return ReservationResponse.from(savedReservation); + } + + @DeleteMapping("/reservations/{reservation-id}") + public void deleteReservation(@PathVariable("reservation-id") final Long reservationId) { + reservationRepository.deleteById(reservationId); + } +} diff --git a/src/main/java/roomescape/controller/AdminWebController.java b/src/main/java/roomescape/controller/AdminWebController.java new file mode 100644 index 000000000..fc9dadb2e --- /dev/null +++ b/src/main/java/roomescape/controller/AdminWebController.java @@ -0,0 +1,18 @@ +package roomescape.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class AdminWebController { + + @GetMapping("/admin") + public String getAdminPage() { + return "/admin/index"; + } + + @GetMapping("/admin/reservation") + public String getReservationPage() { + return "/admin/reservation-legacy"; + } +} diff --git a/src/main/java/roomescape/domain/ClientName.java b/src/main/java/roomescape/domain/ClientName.java new file mode 100644 index 000000000..4de3fac10 --- /dev/null +++ b/src/main/java/roomescape/domain/ClientName.java @@ -0,0 +1,22 @@ +package roomescape.domain; + +public class ClientName { + private static final int MAXIMUM_ENABLE_NAME_LENGTH = 5; + + private final String value; + + public ClientName(final String value) { + validateClientName(value); + this.value = value; + } + + private void validateClientName(final String value) { + if (value == null || value.isEmpty() || value.length() > MAXIMUM_ENABLE_NAME_LENGTH) { + throw new IllegalArgumentException("예약자 이름은 1글자 이상 5글자 이하여야 합니다."); + } + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java new file mode 100644 index 000000000..fa323294e --- /dev/null +++ b/src/main/java/roomescape/domain/Reservation.java @@ -0,0 +1,35 @@ +package roomescape.domain; + +import java.time.LocalDateTime; + +public class Reservation { + private final Long id; + private final ClientName clientName; + private final LocalDateTime time; + + public Reservation(final ClientName clientName, final LocalDateTime time) { + this(0L, clientName, time); + } + + private Reservation(final Long id, final ClientName clientName, final LocalDateTime time) { + this.id = id; + this.clientName = clientName; + this.time = time; + } + + public Reservation initializeIndex(final Long reservationId) { + return new Reservation(reservationId, clientName, time); + } + + public Long getId() { + return id; + } + + public ClientName getClientName() { + return clientName; + } + + public LocalDateTime getTime() { + return time; + } +} diff --git a/src/main/java/roomescape/dto/ReservationResponse.java b/src/main/java/roomescape/dto/ReservationResponse.java new file mode 100644 index 000000000..dbb2a2ea9 --- /dev/null +++ b/src/main/java/roomescape/dto/ReservationResponse.java @@ -0,0 +1,17 @@ +package roomescape.dto; + +import roomescape.domain.Reservation; + +import java.time.LocalDate; +import java.time.LocalTime; + +public record ReservationResponse(Long id, String name, LocalDate date, LocalTime time) { + public static ReservationResponse from(final Reservation reservation) { + return new ReservationResponse( + reservation.getId(), + reservation.getClientName().getValue(), + reservation.getTime().toLocalDate(), + reservation.getTime().toLocalTime() + ); + } +} diff --git a/src/main/java/roomescape/dto/SaveReservationRequest.java b/src/main/java/roomescape/dto/SaveReservationRequest.java new file mode 100644 index 000000000..812d5f601 --- /dev/null +++ b/src/main/java/roomescape/dto/SaveReservationRequest.java @@ -0,0 +1,17 @@ +package roomescape.dto; + +import roomescape.domain.ClientName; +import roomescape.domain.Reservation; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +public record SaveReservationRequest(LocalDate date, String name, LocalTime time) { + public Reservation toReservation() { + return new Reservation( + new ClientName(name), + LocalDateTime.of(date, time) + ); + } +} diff --git a/src/main/java/roomescape/exception/ReservationExceptionHandler.java b/src/main/java/roomescape/exception/ReservationExceptionHandler.java new file mode 100644 index 000000000..7e3dc803c --- /dev/null +++ b/src/main/java/roomescape/exception/ReservationExceptionHandler.java @@ -0,0 +1,15 @@ +package roomescape.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ReservationExceptionHandler { + + @ExceptionHandler + protected ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/roomescape/repository/MemoryReservationRepository.java b/src/main/java/roomescape/repository/MemoryReservationRepository.java new file mode 100644 index 000000000..b022d91b1 --- /dev/null +++ b/src/main/java/roomescape/repository/MemoryReservationRepository.java @@ -0,0 +1,48 @@ +package roomescape.repository; + +import org.springframework.stereotype.Repository; +import roomescape.domain.Reservation; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicLong; + +@Repository +public class MemoryReservationRepository implements ReservationRepository { + private final Map reservations; + + private AtomicLong index; + + public MemoryReservationRepository() { + this.reservations = new HashMap<>(); + this.index = new AtomicLong(0); + } + + @Override + public List findAll() { + return reservations.values() + .stream() + .toList(); + } + + @Override + public Reservation save(final Reservation reservation) { + Long reservationIndex = index.incrementAndGet(); + index = new AtomicLong(reservationIndex); + Reservation savedReservation = reservation.initializeIndex(reservationIndex); + reservations.put(reservationIndex, savedReservation); + + return savedReservation; + } + + @Override + public void deleteById(final Long reservationId) { + if (!reservations.containsKey(reservationId)) { + throw new NoSuchElementException("존재하지 않는 예약입니다."); + } + + reservations.remove(reservationId); + } +} diff --git a/src/main/java/roomescape/repository/ReservationRepository.java b/src/main/java/roomescape/repository/ReservationRepository.java new file mode 100644 index 000000000..2ae812f16 --- /dev/null +++ b/src/main/java/roomescape/repository/ReservationRepository.java @@ -0,0 +1,13 @@ +package roomescape.repository; + +import roomescape.domain.Reservation; + +import java.util.List; + +public interface ReservationRepository { + List findAll(); + + Reservation save(Reservation reservation); + + void deleteById(Long reservationId); +} diff --git a/src/main/resources/templates/admin/index.html b/src/main/resources/templates/admin/index.html index 417102cf1..67ded526d 100644 --- a/src/main/resources/templates/admin/index.html +++ b/src/main/resources/templates/admin/index.html @@ -4,9 +4,8 @@ 방탈출 어드민 - - - + + @@ -22,10 +21,10 @@ diff --git a/src/main/resources/templates/admin/reservation-legacy.html b/src/main/resources/templates/admin/reservation-legacy.html index 320ef3c70..e7d1a35e4 100644 --- a/src/main/resources/templates/admin/reservation-legacy.html +++ b/src/main/resources/templates/admin/reservation-legacy.html @@ -22,10 +22,10 @@ diff --git a/src/main/resources/templates/admin/reservation.html b/src/main/resources/templates/admin/reservation.html index f31714eb7..9660ce213 100644 --- a/src/main/resources/templates/admin/reservation.html +++ b/src/main/resources/templates/admin/reservation.html @@ -22,10 +22,10 @@ diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 000000000..701f52e51 --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + +

Kelly & Anna Welcome Page!

+

Spring 너무 어렵다...

+ + diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java deleted file mode 100644 index 6f7b19791..000000000 --- a/src/test/java/roomescape/MissionStepTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package roomescape; - -import io.restassured.RestAssured; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) -public class MissionStepTest { - - @Test - void 일단계() { - RestAssured.given().log().all() - .when().get("/") - .then().log().all() - .statusCode(200); - } - -} diff --git a/src/test/java/roomescape/controller/AdminApiControllerTest.java b/src/test/java/roomescape/controller/AdminApiControllerTest.java new file mode 100644 index 000000000..54e773cc1 --- /dev/null +++ b/src/test/java/roomescape/controller/AdminApiControllerTest.java @@ -0,0 +1,70 @@ +package roomescape.controller; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.is; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +class AdminApiControllerTest { + + @BeforeEach + public void initReservation() { + for (int i = 0; i < 4; i++) { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2023-08-05"); + params.put("time", "15:40"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations"); + } + } + + @DisplayName("전체 예약 정보를 조회한다.") + @Test + void getReservationsTest() { + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(4)); + } + + @DisplayName("예약 정보를 저장한다.") + @Test + void saveReservationTest() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2023-08-05"); + params.put("time", "15:40"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(200) + .body("id", is(5)); + } + + @DisplayName("예약 정보를 삭제한다.") + @Test + void deleteReservationTest() { + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(200); + } +} diff --git a/src/test/java/roomescape/controller/AdminWebControllerTest.java b/src/test/java/roomescape/controller/AdminWebControllerTest.java new file mode 100644 index 000000000..b9fb1d2e4 --- /dev/null +++ b/src/test/java/roomescape/controller/AdminWebControllerTest.java @@ -0,0 +1,30 @@ +package roomescape.controller; + +import io.restassured.RestAssured; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +class AdminWebControllerTest { + + @DisplayName("/admin으로 요청하면 200응답이 넘어온다.") + @Test + void requestAdminPageTest() { + RestAssured.given().log().all() + .when().get("/admin") + .then().log().all() + .statusCode(200); + } + + @DisplayName("/admin으로 요청하면 200응답이 넘어온다.") + @Test + void requestReservationPageTest() { + RestAssured.given().log().all() + .when().get("/admin/reservation") + .then().log().all() + .statusCode(200); + } +} diff --git a/src/test/java/roomescape/domain/ClientNameTest.java b/src/test/java/roomescape/domain/ClientNameTest.java new file mode 100644 index 000000000..f10b6fc2f --- /dev/null +++ b/src/test/java/roomescape/domain/ClientNameTest.java @@ -0,0 +1,22 @@ +package roomescape.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ClientNameTest { + + @DisplayName("유효하지 않은 길이의 이름이 입력되면 예외를 발생시킨다.") + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {"kellykelly"}) + void playerNameLengthTest(String invalidName) { + // When & Then + assertThatThrownBy(() -> new ClientName(invalidName)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("예약자 이름은 1글자 이상 5글자 이하여야 합니다."); + } +} diff --git a/src/test/java/roomescape/domain/ReservationTest.java b/src/test/java/roomescape/domain/ReservationTest.java new file mode 100644 index 000000000..6c3733409 --- /dev/null +++ b/src/test/java/roomescape/domain/ReservationTest.java @@ -0,0 +1,27 @@ +package roomescape.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +class ReservationTest { + + @DisplayName("인덱스를 입력하면 해당 아이디를 가진 Reservation 객체를 생성해서 반환한다.") + @Test + void initializeIndex() { + // Given + Reservation reservation = new Reservation + (new ClientName("켈리"), + LocalDateTime.of(2023, 1, 12, 10, 12)); + Long initialIndex = 3L; + + // When + Reservation initIndexReservation = reservation.initializeIndex(initialIndex); + + // Then + assertThat(initIndexReservation.getId()).isEqualTo(initialIndex); + } +} diff --git a/src/test/java/roomescape/dto/ReservationResponseTest.java b/src/test/java/roomescape/dto/ReservationResponseTest.java new file mode 100644 index 000000000..9a56efdd7 --- /dev/null +++ b/src/test/java/roomescape/dto/ReservationResponseTest.java @@ -0,0 +1,29 @@ +package roomescape.dto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import roomescape.domain.ClientName; +import roomescape.domain.Reservation; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +class ReservationResponseTest { + + @DisplayName("Reservation을 입력받으면 ReservationResponse로 변환한다.") + @Test + void convertDtoTest() { + // Given + Reservation reservation = new Reservation( + new ClientName("켈리"), + LocalDateTime.of(2023, 1, 12, 1, 12) + ); + + // When + ReservationResponse reservationResponse = ReservationResponse.from(reservation); + + // Then + assertThat(reservationResponse).isNotNull(); + } +} diff --git a/src/test/java/roomescape/dto/SaveReservationRequestTest.java b/src/test/java/roomescape/dto/SaveReservationRequestTest.java new file mode 100644 index 000000000..d8c58ac54 --- /dev/null +++ b/src/test/java/roomescape/dto/SaveReservationRequestTest.java @@ -0,0 +1,30 @@ +package roomescape.dto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import roomescape.domain.Reservation; + +import java.time.LocalDate; +import java.time.LocalTime; + +import static org.assertj.core.api.Assertions.assertThat; + +class SaveReservationRequestTest { + + @DisplayName("SaveReservationRequest를 Reservation으로 변환한다.") + @Test + void toReservationTest() { + // Given + SaveReservationRequest request = new SaveReservationRequest( + LocalDate.of(2023, 12, 1), + "켈리", + LocalTime.of(1, 12) + ); + + // When + Reservation reservation = request.toReservation(); + + // Then + assertThat(reservation).isNotNull(); + } +} diff --git a/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java b/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java new file mode 100644 index 000000000..f1b24b080 --- /dev/null +++ b/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java @@ -0,0 +1,74 @@ +package roomescape.repository; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import roomescape.domain.ClientName; +import roomescape.domain.Reservation; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.NoSuchElementException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class MemoryReservationRepositoryTest { + private MemoryReservationRepository memoryReservationRepository; + + @BeforeEach + public void initRepository() { + memoryReservationRepository = new MemoryReservationRepository(); + Reservation reservation = new Reservation( + new ClientName("켈리"), + LocalDateTime.of(2023, 1, 12, 1, 12) + ); + memoryReservationRepository.save(reservation); + } + + @DisplayName("예약 정보를 저장한다.") + @Test + void saveTest() { + // Given + Reservation reservation = new Reservation( + new ClientName("켈리"), + LocalDateTime.of(2023, 1, 12, 1, 12) + ); + + // When + Reservation savedReservation = memoryReservationRepository.save(reservation); + + // Then + assertThat(savedReservation.getId()).isEqualTo(2L); + } + + @DisplayName("예약 정보를 전체 조회한다.") + @Test + void findAllTest() { + // When + List reservations = memoryReservationRepository.findAll(); + + // Then + assertThat(reservations).hasSize(1); + } + + @DisplayName("예약 정보를 삭제한다.") + @Test + void deleteByIdTest() { + // When + memoryReservationRepository.deleteById(1L); + + // Then + List reservations = memoryReservationRepository.findAll(); + assertThat(reservations).hasSize(0); + } + + @DisplayName("존재하지 않는 예약 정보를 삭제하려고하면 예외가 발생한다.") + @Test + void throwExceptionWhenDeleteNotExistReservationTest() { + // When & Then + assertThatThrownBy(() -> memoryReservationRepository.deleteById(2L)) + .isInstanceOf(NoSuchElementException.class) + .hasMessage("존재하지 않는 예약입니다."); + } +} From 5e2a6767d907f672d7e2317cc8c6310f1b5716ca Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Sun, 21 Apr 2024 22:45:03 +0900 Subject: [PATCH 02/15] =?UTF-8?q?build:=20build.gradle=20=EA=B0=9C?= =?UTF-8?q?=ED=96=89=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fdb3c9f06..2b9dfb56f 100644 --- a/build.gradle +++ b/build.gradle @@ -13,12 +13,23 @@ repositories { } dependencies { + // Spring Boot Starter implementation 'org.springframework.boot:spring-boot-starter' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + + // Spring Web implementation 'org.springframework.boot:spring-boot-starter-web' + + // Thymeleaf implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + + // JDBC implementation 'org.springframework.boot:spring-boot-starter-jdbc' + + // h2 runtimeOnly 'com.h2database:h2' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + + // REST-Assured testImplementation 'io.rest-assured:rest-assured:5.3.1' } From 50ab539ad9bf0d32a497b50982e74b55ec49e9c2 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 21:47:27 +0900 Subject: [PATCH 03/15] =?UTF-8?q?refactor:=20Reservation=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20Reservation=EC=9D=98=20LocalDateTime=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20LocalDate=EC=99=80=20LocalTime=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=ED=95=A8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/domain/Reservation.java | 21 ++++++++++++------- .../roomescape/dto/ReservationResponse.java | 4 ++-- .../dto/SaveReservationRequest.java | 3 ++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index fa323294e..da25a5b28 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -1,24 +1,27 @@ package roomescape.domain; -import java.time.LocalDateTime; +import java.time.LocalDate; +import java.time.LocalTime; public class Reservation { private final Long id; private final ClientName clientName; - private final LocalDateTime time; + private final LocalDate date; + private final LocalTime time; - public Reservation(final ClientName clientName, final LocalDateTime time) { - this(0L, clientName, time); + public Reservation(final ClientName clientName, final LocalDate date, final LocalTime time) { + this(0L, clientName, date, time); } - private Reservation(final Long id, final ClientName clientName, final LocalDateTime time) { + public Reservation(final Long id, final ClientName clientName, final LocalDate date, final LocalTime time) { this.id = id; this.clientName = clientName; + this.date = date; this.time = time; } public Reservation initializeIndex(final Long reservationId) { - return new Reservation(reservationId, clientName, time); + return new Reservation(reservationId, clientName, date, time); } public Long getId() { @@ -29,7 +32,11 @@ public ClientName getClientName() { return clientName; } - public LocalDateTime getTime() { + public LocalDate getDate() { + return date; + } + + public LocalTime getTime() { return time; } } diff --git a/src/main/java/roomescape/dto/ReservationResponse.java b/src/main/java/roomescape/dto/ReservationResponse.java index dbb2a2ea9..50217d037 100644 --- a/src/main/java/roomescape/dto/ReservationResponse.java +++ b/src/main/java/roomescape/dto/ReservationResponse.java @@ -10,8 +10,8 @@ public static ReservationResponse from(final Reservation reservation) { return new ReservationResponse( reservation.getId(), reservation.getClientName().getValue(), - reservation.getTime().toLocalDate(), - reservation.getTime().toLocalTime() + reservation.getDate(), + reservation.getTime() ); } } diff --git a/src/main/java/roomescape/dto/SaveReservationRequest.java b/src/main/java/roomescape/dto/SaveReservationRequest.java index 812d5f601..e8ed37423 100644 --- a/src/main/java/roomescape/dto/SaveReservationRequest.java +++ b/src/main/java/roomescape/dto/SaveReservationRequest.java @@ -11,7 +11,8 @@ public record SaveReservationRequest(LocalDate date, String name, LocalTime time public Reservation toReservation() { return new Reservation( new ClientName(name), - LocalDateTime.of(date, time) + date, + time ); } } From b378849d02495146738aa5c37daa0ec7b990bed4 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 21:48:45 +0900 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20h2=20DB=20=EA=B8=B0=EB=B0=98=20Re?= =?UTF-8?q?pository=20=EA=B0=9D=EC=B2=B4=20=EA=B5=AC=ED=98=84=20h2=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=B2=A0=EC=9D=B4=EC=8A=A4=EB=A5=BC?= =?UTF-8?q?=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EB=8A=94=20Repository=EB=A5=BC=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=A8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/H2ReservationRepository.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/main/java/roomescape/repository/H2ReservationRepository.java diff --git a/src/main/java/roomescape/repository/H2ReservationRepository.java b/src/main/java/roomescape/repository/H2ReservationRepository.java new file mode 100644 index 000000000..47d395716 --- /dev/null +++ b/src/main/java/roomescape/repository/H2ReservationRepository.java @@ -0,0 +1,62 @@ +package roomescape.repository; + +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import roomescape.domain.ClientName; +import roomescape.domain.Reservation; + +import java.util.List; +import java.util.Map; + +@Repository +public class H2ReservationRepository implements ReservationRepository { + private final NamedParameterJdbcTemplate template; + + public H2ReservationRepository(final NamedParameterJdbcTemplate template) { + this.template = template; + } + + @Override + public List findAll() { + String sql = "SELECT id, name, date, time FROM reservation"; + + List reservations = template.query(sql, itemRowMapper()); + return reservations; + } + + private RowMapper itemRowMapper() { + return ((rs, rowNum) -> new Reservation( + rs.getLong("id"), + new ClientName(rs.getString("name")), + rs.getDate("date").toLocalDate(), + rs.getTime("time").toLocalTime() + )); + } + + @Override + public Reservation save(final Reservation reservation) { + String sql = "INSERT INTO reservation(name, date, time) VALUES (:name, :date, :time)"; + MapSqlParameterSource param = new MapSqlParameterSource() + .addValue("name", reservation.getClientName().getValue()) + .addValue("date", reservation.getDate()) + .addValue("time", reservation.getTime()); + KeyHolder keyHolder = new GeneratedKeyHolder(); + template.update(sql, param, keyHolder); + + long savedReservationId = keyHolder.getKey().longValue(); + + return reservation.initializeIndex(savedReservationId); + } + + @Override + public void deleteById(final Long reservationId) { + String sql = "DELETE FROM reservation WHERE id = :id"; + Map param = Map.of("id", reservationId); + + template.update(sql, param); + } +} From 090da4c698f1d0819e1823bed6cb5cdb9459543f Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 21:49:11 +0900 Subject: [PATCH 05/15] =?UTF-8?q?config:=20Repository=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=EC=9D=84=20H2ReservationRepository=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/config/AppConfig.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/roomescape/config/AppConfig.java diff --git a/src/main/java/roomescape/config/AppConfig.java b/src/main/java/roomescape/config/AppConfig.java new file mode 100644 index 000000000..e56b0e39b --- /dev/null +++ b/src/main/java/roomescape/config/AppConfig.java @@ -0,0 +1,22 @@ +package roomescape.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import roomescape.repository.H2ReservationRepository; +import roomescape.repository.ReservationRepository; + +@Configuration +public class AppConfig { + + private final NamedParameterJdbcTemplate template; + + public AppConfig(final NamedParameterJdbcTemplate template) { + this.template = template; + } + + @Bean + public ReservationRepository reservationRepository() { + return new H2ReservationRepository(template); + } +} From e94d84e6aff5801e768f4f8ce7c6b0ba2928efd6 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 21:49:57 +0900 Subject: [PATCH 06/15] =?UTF-8?q?test:=20Reservation=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=97=90=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20=EC=98=81=ED=96=A5=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/roomescape/domain/ReservationTest.java | 11 +++++++---- .../java/roomescape/dto/ReservationResponseTest.java | 6 ++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/test/java/roomescape/domain/ReservationTest.java b/src/test/java/roomescape/domain/ReservationTest.java index 6c3733409..c12968ec5 100644 --- a/src/test/java/roomescape/domain/ReservationTest.java +++ b/src/test/java/roomescape/domain/ReservationTest.java @@ -3,7 +3,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; +import java.time.LocalDate; +import java.time.LocalTime; import static org.assertj.core.api.Assertions.assertThat; @@ -13,9 +14,11 @@ class ReservationTest { @Test void initializeIndex() { // Given - Reservation reservation = new Reservation - (new ClientName("켈리"), - LocalDateTime.of(2023, 1, 12, 10, 12)); + Reservation reservation = new Reservation( + new ClientName("켈리"), + LocalDate.of(2023, 1, 12), + LocalTime.of(1, 12)); + Long initialIndex = 3L; // When diff --git a/src/test/java/roomescape/dto/ReservationResponseTest.java b/src/test/java/roomescape/dto/ReservationResponseTest.java index 9a56efdd7..4971f8465 100644 --- a/src/test/java/roomescape/dto/ReservationResponseTest.java +++ b/src/test/java/roomescape/dto/ReservationResponseTest.java @@ -5,7 +5,8 @@ import roomescape.domain.ClientName; import roomescape.domain.Reservation; -import java.time.LocalDateTime; +import java.time.LocalDate; +import java.time.LocalTime; import static org.assertj.core.api.Assertions.assertThat; @@ -17,7 +18,8 @@ void convertDtoTest() { // Given Reservation reservation = new Reservation( new ClientName("켈리"), - LocalDateTime.of(2023, 1, 12, 1, 12) + LocalDate.of(2023, 1, 12), + LocalTime.of(1, 12) ); // When From c3acdfb97ce9f62d9ab5d15c42e1dfc09c87493f Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 21:50:29 +0900 Subject: [PATCH 07/15] =?UTF-8?q?test:=20=EC=98=88=EC=95=BD=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=82=AD=EC=A0=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/controller/AdminApiControllerTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/roomescape/controller/AdminApiControllerTest.java b/src/test/java/roomescape/controller/AdminApiControllerTest.java index 54e773cc1..da3cf9d0a 100644 --- a/src/test/java/roomescape/controller/AdminApiControllerTest.java +++ b/src/test/java/roomescape/controller/AdminApiControllerTest.java @@ -7,10 +7,13 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import roomescape.dto.ReservationResponse; import java.util.HashMap; +import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @@ -66,5 +69,13 @@ void deleteReservationTest() { .when().delete("/reservations/1") .then().log().all() .statusCode(200); + + List reservations = RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200).extract() + .jsonPath().getList(".", ReservationResponse.class); + + assertThat(reservations.size()).isEqualTo(3); } } From 3ecafa295dabb1a1f6253dc32da2d15e5406e972 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 22:03:48 +0900 Subject: [PATCH 08/15] =?UTF-8?q?test:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EB=AA=A8=EB=A6=AC=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20Disabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemoryReservationRepositoryTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java b/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java index f1b24b080..9c3f53ce4 100644 --- a/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java +++ b/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java @@ -1,18 +1,21 @@ package roomescape.repository; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import roomescape.domain.ClientName; import roomescape.domain.Reservation; -import java.time.LocalDateTime; +import java.time.LocalDate; +import java.time.LocalTime; import java.util.List; import java.util.NoSuchElementException; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@Disabled class MemoryReservationRepositoryTest { private MemoryReservationRepository memoryReservationRepository; @@ -21,8 +24,8 @@ public void initRepository() { memoryReservationRepository = new MemoryReservationRepository(); Reservation reservation = new Reservation( new ClientName("켈리"), - LocalDateTime.of(2023, 1, 12, 1, 12) - ); + LocalDate.of(2023, 1, 12), + LocalTime.of(1, 12)); memoryReservationRepository.save(reservation); } @@ -32,8 +35,8 @@ void saveTest() { // Given Reservation reservation = new Reservation( new ClientName("켈리"), - LocalDateTime.of(2023, 1, 12, 1, 12) - ); + LocalDate.of(2023, 1, 12), + LocalTime.of(1, 12)); // When Reservation savedReservation = memoryReservationRepository.save(reservation); From 721df43597ab96ff41a58e59e031b4aa03d7c917 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 22:04:17 +0900 Subject: [PATCH 09/15] =?UTF-8?q?config:=20=EC=98=88=EC=95=BD=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20SQL=EC=9E=91=EC=84=B1=20=EB=B0=8F=20applic?= =?UTF-8?q?ation.properties=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 2 ++ src/main/resources/schema.sql | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 src/main/resources/schema.sql diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e69de29bb..e90e3b2d9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.h2.console.enabled=true +spring.datasource.url=jdbc:h2:mem:database diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 000000000..8d9ab2754 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE reservation +( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + date VARCHAR(255) NOT NULL, + time VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); From f137a337f1de808418fad309db51652d4d64e31e Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 22:09:24 +0900 Subject: [PATCH 10/15] =?UTF-8?q?refactor:=20AdminApiController=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EA=B0=92=EC=9D=84=20=EB=B0=94=EB=A1=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=84=B4=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/controller/AdminApiController.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/roomescape/controller/AdminApiController.java b/src/main/java/roomescape/controller/AdminApiController.java index a4f94e888..714e48e71 100644 --- a/src/main/java/roomescape/controller/AdminApiController.java +++ b/src/main/java/roomescape/controller/AdminApiController.java @@ -23,12 +23,10 @@ public AdminApiController(final ReservationRepository reservationRepository) { @GetMapping("/reservations") public List getReservations() { - List reservations = reservationRepository.findAll() + return reservationRepository.findAll() .stream() .map(ReservationResponse::from) .toList(); - - return reservations; } @PostMapping("/reservations") From 2eca83ceb2c844bdb133500066aca3a994658a07 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 22:11:09 +0900 Subject: [PATCH 11/15] =?UTF-8?q?refactor:=20Reservation=20=EB=A7=A4?= =?UTF-8?q?=EC=A7=81=EB=84=98=EB=B2=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/domain/Reservation.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index da25a5b28..ab2aca8a9 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -4,13 +4,15 @@ import java.time.LocalTime; public class Reservation { + private static final Long DEFAULT_ID_VALUE = 0L; + private final Long id; private final ClientName clientName; private final LocalDate date; private final LocalTime time; public Reservation(final ClientName clientName, final LocalDate date, final LocalTime time) { - this(0L, clientName, date, time); + this(DEFAULT_ID_VALUE, clientName, date, time); } public Reservation(final Long id, final ClientName clientName, final LocalDate date, final LocalTime time) { From 76a90b00e34dd64b5038e92e2458ed0b5c1f2dcd Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 22:14:50 +0900 Subject: [PATCH 12/15] =?UTF-8?q?test:=20ReservationResponseTest=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/roomescape/dto/ReservationResponseTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/roomescape/dto/ReservationResponseTest.java b/src/test/java/roomescape/dto/ReservationResponseTest.java index 4971f8465..aa2bdf00d 100644 --- a/src/test/java/roomescape/dto/ReservationResponseTest.java +++ b/src/test/java/roomescape/dto/ReservationResponseTest.java @@ -17,6 +17,7 @@ class ReservationResponseTest { void convertDtoTest() { // Given Reservation reservation = new Reservation( + 1L, new ClientName("켈리"), LocalDate.of(2023, 1, 12), LocalTime.of(1, 12) @@ -27,5 +28,9 @@ void convertDtoTest() { // Then assertThat(reservationResponse).isNotNull(); + assertThat(reservationResponse.id()).isEqualTo(reservation.getId()); + assertThat(reservationResponse.name()).isEqualTo(reservation.getClientName().getValue()); + assertThat(reservationResponse.date()).isEqualTo(reservation.getDate()); + assertThat(reservationResponse.time()).isEqualTo(reservation.getTime()); } } From d1ed4c3da2a473e4a4f0454eabff508f0896f560 Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 22:17:12 +0900 Subject: [PATCH 13/15] =?UTF-8?q?refactor:=20ReservationExceptionHandler?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/exception/ReservationExceptionHandler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/roomescape/exception/ReservationExceptionHandler.java b/src/main/java/roomescape/exception/ReservationExceptionHandler.java index 7e3dc803c..8e356389b 100644 --- a/src/main/java/roomescape/exception/ReservationExceptionHandler.java +++ b/src/main/java/roomescape/exception/ReservationExceptionHandler.java @@ -1,6 +1,5 @@ package roomescape.exception; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -10,6 +9,6 @@ public class ReservationExceptionHandler { @ExceptionHandler protected ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { - return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); + return ResponseEntity.badRequest().body(e.getMessage()); } } From ff663c8ed6d9e091110e89fcd6f51f18fdd9d68a Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Mon, 22 Apr 2024 22:20:01 +0900 Subject: [PATCH 14/15] =?UTF-8?q?refactor:=20SaveReservationRequest=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20import=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/dto/SaveReservationRequest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/roomescape/dto/SaveReservationRequest.java b/src/main/java/roomescape/dto/SaveReservationRequest.java index e8ed37423..629baf616 100644 --- a/src/main/java/roomescape/dto/SaveReservationRequest.java +++ b/src/main/java/roomescape/dto/SaveReservationRequest.java @@ -4,7 +4,6 @@ import roomescape.domain.Reservation; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.LocalTime; public record SaveReservationRequest(LocalDate date, String name, LocalTime time) { From 2e8474e3ad274532c0d3431a792d065ee353920e Mon Sep 17 00:00:00 2001 From: kelly6bf Date: Tue, 23 Apr 2024 22:37:30 +0900 Subject: [PATCH 15/15] =?UTF-8?q?=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/domain/Reservation.java | 9 +++ .../roomescape/exception/ErrorResponse.java | 4 + .../ReservationExceptionHandler.java | 11 ++- .../MemoryReservationRepository.java | 48 ------------ .../controller/AdminApiControllerTest.java | 8 +- .../controller/AdminWebControllerTest.java | 12 ++- .../roomescape/domain/ReservationTest.java | 17 +++- .../MemoryReservationRepositoryTest.java | 77 ------------------- 8 files changed, 56 insertions(+), 130 deletions(-) create mode 100644 src/main/java/roomescape/exception/ErrorResponse.java delete mode 100644 src/main/java/roomescape/repository/MemoryReservationRepository.java delete mode 100644 src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index ab2aca8a9..ec8c77257 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -1,6 +1,7 @@ package roomescape.domain; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; public class Reservation { @@ -16,12 +17,20 @@ public Reservation(final ClientName clientName, final LocalDate date, final Loca } public Reservation(final Long id, final ClientName clientName, final LocalDate date, final LocalTime time) { + validateReservationDateAndTime(date, time); this.id = id; this.clientName = clientName; this.date = date; this.time = time; } + private void validateReservationDateAndTime(final LocalDate date, final LocalTime time) { + LocalDateTime reservationLocalDateTime = LocalDateTime.of(date, time); + if (reservationLocalDateTime.isBefore(LocalDateTime.now())) { + throw new IllegalArgumentException("현재 날짜보다 이전 날짜를 예약할 수 없습니다."); + } + } + public Reservation initializeIndex(final Long reservationId) { return new Reservation(reservationId, clientName, date, time); } diff --git a/src/main/java/roomescape/exception/ErrorResponse.java b/src/main/java/roomescape/exception/ErrorResponse.java new file mode 100644 index 000000000..ebe97cc4a --- /dev/null +++ b/src/main/java/roomescape/exception/ErrorResponse.java @@ -0,0 +1,4 @@ +package roomescape.exception; + +public record ErrorResponse(String message) { +} diff --git a/src/main/java/roomescape/exception/ReservationExceptionHandler.java b/src/main/java/roomescape/exception/ReservationExceptionHandler.java index 8e356389b..3f5bb9bab 100644 --- a/src/main/java/roomescape/exception/ReservationExceptionHandler.java +++ b/src/main/java/roomescape/exception/ReservationExceptionHandler.java @@ -4,11 +4,18 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.NoSuchElementException; + @RestControllerAdvice public class ReservationExceptionHandler { @ExceptionHandler - protected ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { - return ResponseEntity.badRequest().body(e.getMessage()); + protected ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) { + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); + } + + @ExceptionHandler + protected ResponseEntity handleNoSuchElementException(NoSuchElementException e) { + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } } diff --git a/src/main/java/roomescape/repository/MemoryReservationRepository.java b/src/main/java/roomescape/repository/MemoryReservationRepository.java deleted file mode 100644 index b022d91b1..000000000 --- a/src/main/java/roomescape/repository/MemoryReservationRepository.java +++ /dev/null @@ -1,48 +0,0 @@ -package roomescape.repository; - -import org.springframework.stereotype.Repository; -import roomescape.domain.Reservation; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.atomic.AtomicLong; - -@Repository -public class MemoryReservationRepository implements ReservationRepository { - private final Map reservations; - - private AtomicLong index; - - public MemoryReservationRepository() { - this.reservations = new HashMap<>(); - this.index = new AtomicLong(0); - } - - @Override - public List findAll() { - return reservations.values() - .stream() - .toList(); - } - - @Override - public Reservation save(final Reservation reservation) { - Long reservationIndex = index.incrementAndGet(); - index = new AtomicLong(reservationIndex); - Reservation savedReservation = reservation.initializeIndex(reservationIndex); - reservations.put(reservationIndex, savedReservation); - - return savedReservation; - } - - @Override - public void deleteById(final Long reservationId) { - if (!reservations.containsKey(reservationId)) { - throw new NoSuchElementException("존재하지 않는 예약입니다."); - } - - reservations.remove(reservationId); - } -} diff --git a/src/test/java/roomescape/controller/AdminApiControllerTest.java b/src/test/java/roomescape/controller/AdminApiControllerTest.java index da3cf9d0a..2c1973bee 100644 --- a/src/test/java/roomescape/controller/AdminApiControllerTest.java +++ b/src/test/java/roomescape/controller/AdminApiControllerTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.test.annotation.DirtiesContext; import roomescape.dto.ReservationResponse; @@ -16,12 +17,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) class AdminApiControllerTest { + @LocalServerPort + int randomServerPort; + @BeforeEach public void initReservation() { + RestAssured.port = randomServerPort; + for (int i = 0; i < 4; i++) { Map params = new HashMap<>(); params.put("name", "브라운"); diff --git a/src/test/java/roomescape/controller/AdminWebControllerTest.java b/src/test/java/roomescape/controller/AdminWebControllerTest.java index b9fb1d2e4..56f3a3dd7 100644 --- a/src/test/java/roomescape/controller/AdminWebControllerTest.java +++ b/src/test/java/roomescape/controller/AdminWebControllerTest.java @@ -1,15 +1,25 @@ package roomescape.controller; import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.test.annotation.DirtiesContext; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) class AdminWebControllerTest { + @LocalServerPort + int randomServerPort; + + @BeforeEach + public void setup() { + RestAssured.port = randomServerPort; + } + @DisplayName("/admin으로 요청하면 200응답이 넘어온다.") @Test void requestAdminPageTest() { diff --git a/src/test/java/roomescape/domain/ReservationTest.java b/src/test/java/roomescape/domain/ReservationTest.java index c12968ec5..99c4e43bc 100644 --- a/src/test/java/roomescape/domain/ReservationTest.java +++ b/src/test/java/roomescape/domain/ReservationTest.java @@ -7,6 +7,7 @@ import java.time.LocalTime; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class ReservationTest { @@ -16,7 +17,7 @@ void initializeIndex() { // Given Reservation reservation = new Reservation( new ClientName("켈리"), - LocalDate.of(2023, 1, 12), + LocalDate.now().plusDays(1), LocalTime.of(1, 12)); Long initialIndex = 3L; @@ -27,4 +28,18 @@ void initializeIndex() { // Then assertThat(initIndexReservation.getId()).isEqualTo(initialIndex); } + + @DisplayName("현재 날짜/시간보다 이전의 예약 정보를 입력하면 예외가 발생한다.") + @Test + void throwExceptionWithReservationDateTimeBeforeNow() { + // Given + ClientName clientName = new ClientName("켈리"); + LocalDate dateBeforeNow = LocalDate.now().minusDays(1); + LocalTime time = LocalTime.now(); + + // When & Then + assertThatThrownBy(() -> new Reservation(clientName, dateBeforeNow, time)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("현재 날짜보다 이전 날짜를 예약할 수 없습니다."); + } } diff --git a/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java b/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java deleted file mode 100644 index 9c3f53ce4..000000000 --- a/src/test/java/roomescape/repository/MemoryReservationRepositoryTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package roomescape.repository; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import roomescape.domain.ClientName; -import roomescape.domain.Reservation; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.util.List; -import java.util.NoSuchElementException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -@Disabled -class MemoryReservationRepositoryTest { - private MemoryReservationRepository memoryReservationRepository; - - @BeforeEach - public void initRepository() { - memoryReservationRepository = new MemoryReservationRepository(); - Reservation reservation = new Reservation( - new ClientName("켈리"), - LocalDate.of(2023, 1, 12), - LocalTime.of(1, 12)); - memoryReservationRepository.save(reservation); - } - - @DisplayName("예약 정보를 저장한다.") - @Test - void saveTest() { - // Given - Reservation reservation = new Reservation( - new ClientName("켈리"), - LocalDate.of(2023, 1, 12), - LocalTime.of(1, 12)); - - // When - Reservation savedReservation = memoryReservationRepository.save(reservation); - - // Then - assertThat(savedReservation.getId()).isEqualTo(2L); - } - - @DisplayName("예약 정보를 전체 조회한다.") - @Test - void findAllTest() { - // When - List reservations = memoryReservationRepository.findAll(); - - // Then - assertThat(reservations).hasSize(1); - } - - @DisplayName("예약 정보를 삭제한다.") - @Test - void deleteByIdTest() { - // When - memoryReservationRepository.deleteById(1L); - - // Then - List reservations = memoryReservationRepository.findAll(); - assertThat(reservations).hasSize(0); - } - - @DisplayName("존재하지 않는 예약 정보를 삭제하려고하면 예외가 발생한다.") - @Test - void throwExceptionWhenDeleteNotExistReservationTest() { - // When & Then - assertThatThrownBy(() -> memoryReservationRepository.deleteById(2L)) - .isInstanceOf(NoSuchElementException.class) - .hasMessage("존재하지 않는 예약입니다."); - } -}