From fd7449cab155285f82653dfb3db527ba062584c8 Mon Sep 17 00:00:00 2001 From: bbggr1209 Date: Fri, 5 Jul 2024 17:55:14 +0900 Subject: [PATCH 1/4] =?UTF-8?q?1=EB=8B=A8=EA=B3=84-=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ .../java/roomescape/controller/HomeController.java | 13 +++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 src/main/java/roomescape/controller/HomeController.java diff --git a/build.gradle b/build.gradle index 57267157c..e9541e4de 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' } test { diff --git a/src/main/java/roomescape/controller/HomeController.java b/src/main/java/roomescape/controller/HomeController.java new file mode 100644 index 000000000..e9ce2bd81 --- /dev/null +++ b/src/main/java/roomescape/controller/HomeController.java @@ -0,0 +1,13 @@ +package roomescape.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class HomeController { + + @GetMapping("/") + public String home() { + return "home"; + } +} From 8dc476b2d4c49ef09d274c905a2b4753f35815fb Mon Sep 17 00:00:00 2001 From: bbggr1209 Date: Fri, 5 Jul 2024 19:05:35 +0900 Subject: [PATCH 2/4] =?UTF-8?q?2=EB=8B=A8=EA=B3=84-=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 33 ++++++++++++++++++ .../java/roomescape/model/Reservation.java | 34 +++++++++++++++++++ src/test/java/roomescape/MissionStepTest.java | 15 ++++++++ 3 files changed, 82 insertions(+) create mode 100644 src/main/java/roomescape/controller/ReservationController.java create mode 100644 src/main/java/roomescape/model/Reservation.java diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java new file mode 100644 index 000000000..6799511a9 --- /dev/null +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -0,0 +1,33 @@ +package roomescape.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import roomescape.model.Reservation; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +@Controller +public class ReservationController { + + private List reservations = new ArrayList<>(); + + @GetMapping("/reservation") + public String reservation() { + return "reservation"; + } + + public ReservationController() { + reservations.add(new Reservation(1L, "브라운", LocalDate.of(2023, 1, 1), "10:00")); + reservations.add(new Reservation(2L, "브라운", LocalDate.of(2023, 1, 2), "11:00")); + reservations.add(new Reservation(3L, "브라운", LocalDate.of(2023, 1, 3), "12:00")); + } + + @GetMapping("/reservations") + @ResponseBody + public List getReservations() { + return reservations; + } +} diff --git a/src/main/java/roomescape/model/Reservation.java b/src/main/java/roomescape/model/Reservation.java new file mode 100644 index 000000000..2a134aeb8 --- /dev/null +++ b/src/main/java/roomescape/model/Reservation.java @@ -0,0 +1,34 @@ +package roomescape.model; + +import java.time.LocalDate; + +public class Reservation { + + private Long id; + private String name; + private LocalDate date; + private String time; + + public Reservation(Long id, String name, LocalDate date, String time) { + this.id = id; + this.name = name; + this.date = date; + this.time = time; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public LocalDate getDate() { + return date; + } + + public String getTime() { + return time; + } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index cf4efbe91..df1b065d1 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import static org.hamcrest.Matchers.is; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) @@ -16,4 +17,18 @@ public class MissionStepTest { .then().log().all() .statusCode(200); } + + @Test + void 이단계() { + RestAssured.given().log().all() + .when().get("/reservation") + .then().log().all() + .statusCode(200); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("size()", is(3)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + } } From fdc90c1d697e3dcadcb3bff35165b5b1f91d9002 Mon Sep 17 00:00:00 2001 From: bbggr1209 Date: Sat, 6 Jul 2024 17:02:46 +0900 Subject: [PATCH 3/4] =?UTF-8?q?3=EB=8B=A8=EA=B3=84-=EC=98=88=EC=95=BD=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80/=EC=B7=A8=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 82 ++++++++++++++++--- .../roomescape/dto/ReservationReqDto.java | 29 +++++++ .../roomescape/dto/ReservationResDto.java | 35 ++++++++ .../java/roomescape/model/Reservation.java | 2 + .../template/ApiResponseTemplate.java | 20 +++++ src/test/java/roomescape/MissionStepTest.java | 52 +++++++++++- 6 files changed, 207 insertions(+), 13 deletions(-) create mode 100644 src/main/java/roomescape/dto/ReservationReqDto.java create mode 100644 src/main/java/roomescape/dto/ReservationResDto.java create mode 100644 src/main/java/roomescape/template/ApiResponseTemplate.java diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 6799511a9..2d1114f4f 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -1,33 +1,93 @@ package roomescape.controller; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; +import roomescape.dto.ReservationReqDto; +import roomescape.dto.ReservationResDto; import roomescape.model.Reservation; +import roomescape.template.ApiResponseTemplate; -import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; @Controller public class ReservationController { - private List reservations = new ArrayList<>(); + private final List reservations = new ArrayList<>(); + private final AtomicLong index = new AtomicLong(1); @GetMapping("/reservation") public String reservation() { return "reservation"; } - public ReservationController() { - reservations.add(new Reservation(1L, "브라운", LocalDate.of(2023, 1, 1), "10:00")); - reservations.add(new Reservation(2L, "브라운", LocalDate.of(2023, 1, 2), "11:00")); - reservations.add(new Reservation(3L, "브라운", LocalDate.of(2023, 1, 3), "12:00")); + @GetMapping("/reservations") + @ResponseBody + public ApiResponseTemplate> getReservations() { + List dtoList = reservations.stream() + .map(reservation -> new ReservationResDto( + reservation.getId(), + reservation.getName(), + reservation.getDate(), + reservation.getTime())) + .collect(Collectors.toList()); + + return new ApiResponseTemplate<>("success", dtoList); } - @GetMapping("/reservations") + @PostMapping("/reservations") + @ResponseBody + public ResponseEntity> addReservation(@RequestBody ReservationReqDto reservationReqDto) { + Reservation newReservation = new Reservation( + index.getAndIncrement(), + reservationReqDto.getName(), + reservationReqDto.getDate(), + reservationReqDto.getTime() + ); + reservations.add(newReservation); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Location", "/reservations/" + newReservation.getId()); + + ReservationResDto newReservationResDto = new ReservationResDto( + newReservation.getId(), + newReservation.getName(), + newReservation.getDate(), + newReservation.getTime() + ); + + return new ResponseEntity<>( + new ApiResponseTemplate<>("success", newReservationResDto), + headers, + HttpStatus.CREATED + ); + } + + @DeleteMapping("/reservations/{id}") + @ResponseBody + public ResponseEntity> deleteReservation(@PathVariable Long id) { + boolean removed = reservations.removeIf(reservation -> reservation.getId().equals(id)); + return removed ? createSuccessDeleteResponse() : createNotFoundDeleteResponse(); + } + + private ResponseEntity> createSuccessDeleteResponse() { + return new ResponseEntity<>(new ApiResponseTemplate<>("success", "예약 삭제 성공"), HttpStatus.NO_CONTENT); + } + + private ResponseEntity> createNotFoundDeleteResponse() { + return new ResponseEntity<>(new ApiResponseTemplate<>("error", "예약을 삭제 실패"), HttpStatus.NOT_FOUND); + } + + @PostMapping("/reservations/reset") @ResponseBody - public List getReservations() { - return reservations; + public ResponseEntity resetReservations() { + index.set(1); + reservations.clear(); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } } diff --git a/src/main/java/roomescape/dto/ReservationReqDto.java b/src/main/java/roomescape/dto/ReservationReqDto.java new file mode 100644 index 000000000..a46d70101 --- /dev/null +++ b/src/main/java/roomescape/dto/ReservationReqDto.java @@ -0,0 +1,29 @@ +package roomescape.dto; + +import java.time.LocalDate; + +public class ReservationReqDto { + private String name; + private LocalDate date; + private String time; + + public ReservationReqDto() {} + + public ReservationReqDto(String name, LocalDate date, String time) { + this.name = name; + this.date = date; + this.time = time; + } + + public String getName() { + return name; + } + + public LocalDate getDate() { + return date; + } + + public String getTime() { + return time; + } +} diff --git a/src/main/java/roomescape/dto/ReservationResDto.java b/src/main/java/roomescape/dto/ReservationResDto.java new file mode 100644 index 000000000..389d34c9f --- /dev/null +++ b/src/main/java/roomescape/dto/ReservationResDto.java @@ -0,0 +1,35 @@ +package roomescape.dto; + +import java.time.LocalDate; + +public class ReservationResDto { + private Long id; + private String name; + private LocalDate date; + private String time; + + public ReservationResDto() {} + + public ReservationResDto(Long id, String name, LocalDate date, String time) { + this.id = id; + this.name = name; + this.date = date; + this.time = time; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public LocalDate getDate() { + return date; + } + + public String getTime() { + return time; + } +} diff --git a/src/main/java/roomescape/model/Reservation.java b/src/main/java/roomescape/model/Reservation.java index 2a134aeb8..38bbd2066 100644 --- a/src/main/java/roomescape/model/Reservation.java +++ b/src/main/java/roomescape/model/Reservation.java @@ -9,6 +9,8 @@ public class Reservation { private LocalDate date; private String time; + public Reservation() {} + public Reservation(Long id, String name, LocalDate date, String time) { this.id = id; this.name = name; diff --git a/src/main/java/roomescape/template/ApiResponseTemplate.java b/src/main/java/roomescape/template/ApiResponseTemplate.java new file mode 100644 index 000000000..6c1f62d09 --- /dev/null +++ b/src/main/java/roomescape/template/ApiResponseTemplate.java @@ -0,0 +1,20 @@ +package roomescape.template; + +public class ApiResponseTemplate { + + private final String status; + private final T data; + + public ApiResponseTemplate(String status, T data) { + this.status = status; + this.data = data; + } + + public String getStatus() { + return status; + } + + public T getData() { + return data; + } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index df1b065d1..f19314175 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -1,14 +1,28 @@ package roomescape; import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.BeforeEach; 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) -public class MissionStepTest { +class MissionStepTest { + + @BeforeEach + void 초기화() { + RestAssured.given().log().all() + .when().post("/reservations/reset") + .then().log().all() + .statusCode(204); + } @Test void 일단계() { @@ -29,6 +43,40 @@ public class MissionStepTest { .when().get("/reservations") .then().log().all() .statusCode(200) - .body("size()", is(3)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + .body("data.size()", is(0)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수 만큼 검증하거나 0개임을 확인하세요. + } + + @Test + void 삼단계() { + 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(201) + .header("Location", "/reservations/1") + .body("data.id", is(1)); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("data.size()", is(1)); + + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(204); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("data.size()", is(0)); } } From 44d91ba22a405586ea00790f855cdc78d4a16acd Mon Sep 17 00:00:00 2001 From: bbggr1209 Date: Wed, 10 Jul 2024 13:00:09 +0900 Subject: [PATCH 4/4] =?UTF-8?q?4=EB=8B=A8=EA=B3=84-=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ReservationController.java | 41 ++++++++++++++++--- .../exception/BadRequestException.java | 7 ++++ .../exception/GlobalExceptionHandler.java | 16 ++++++++ src/test/java/roomescape/MissionStepTest.java | 22 ++++++++++ 4 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 src/main/java/roomescape/exception/BadRequestException.java create mode 100644 src/main/java/roomescape/exception/GlobalExceptionHandler.java diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 2d1114f4f..a7a5d890d 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.*; import roomescape.dto.ReservationReqDto; import roomescape.dto.ReservationResDto; +import roomescape.exception.BadRequestException; import roomescape.model.Reservation; import roomescape.template.ApiResponseTemplate; @@ -26,7 +27,12 @@ public String reservation() { return "reservation"; } - @GetMapping("/reservations") + @GetMapping(value = "/reservations", produces = "text/html") + public String reservationPage() { + return "new-reservation"; + } + + @GetMapping(value = "/reservations", produces = "application/json") @ResponseBody public ApiResponseTemplate> getReservations() { List dtoList = reservations.stream() @@ -43,6 +49,8 @@ public ApiResponseTemplate> getReservations() { @PostMapping("/reservations") @ResponseBody public ResponseEntity> addReservation(@RequestBody ReservationReqDto reservationReqDto) { + validateReservationRequest(reservationReqDto); + Reservation newReservation = new Reservation( index.getAndIncrement(), reservationReqDto.getName(), @@ -71,16 +79,39 @@ public ResponseEntity> addReservation(@Re @DeleteMapping("/reservations/{id}") @ResponseBody public ResponseEntity> deleteReservation(@PathVariable Long id) { - boolean removed = reservations.removeIf(reservation -> reservation.getId().equals(id)); - return removed ? createSuccessDeleteResponse() : createNotFoundDeleteResponse(); + ensureReservationExists(id); + removeReservation(id); + return createSuccessDeleteResponse(); } private ResponseEntity> createSuccessDeleteResponse() { return new ResponseEntity<>(new ApiResponseTemplate<>("success", "예약 삭제 성공"), HttpStatus.NO_CONTENT); } - private ResponseEntity> createNotFoundDeleteResponse() { - return new ResponseEntity<>(new ApiResponseTemplate<>("error", "예약을 삭제 실패"), HttpStatus.NOT_FOUND); + private void ensureReservationExists(Long id) { + if (!reservationExists(id)) { + throw new BadRequestException("예약 삭제 실패"); + } + } + + private void validateReservationRequest(ReservationReqDto reservationReqDto) { + if (reservationReqDto.getName() == null || reservationReqDto.getName().isEmpty()) { + throw new BadRequestException("이름을 작성해주세요"); + } + if (reservationReqDto.getDate() == null) { + throw new BadRequestException("날짜를 선택해주세요"); + } + if (reservationReqDto.getTime() == null || reservationReqDto.getTime().isEmpty()) { + throw new BadRequestException("시간을 선택해주세요"); + } + } + + private boolean reservationExists(Long id) { + return reservations.stream().anyMatch(reservation -> reservation.getId().equals(id)); + } + + private void removeReservation(Long id) { + reservations.removeIf(reservation -> reservation.getId().equals(id)); } @PostMapping("/reservations/reset") diff --git a/src/main/java/roomescape/exception/BadRequestException.java b/src/main/java/roomescape/exception/BadRequestException.java new file mode 100644 index 000000000..aebe2a07e --- /dev/null +++ b/src/main/java/roomescape/exception/BadRequestException.java @@ -0,0 +1,7 @@ +package roomescape.exception; + +public class BadRequestException extends IllegalArgumentException { + public BadRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/roomescape/exception/GlobalExceptionHandler.java b/src/main/java/roomescape/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..76e204e13 --- /dev/null +++ b/src/main/java/roomescape/exception/GlobalExceptionHandler.java @@ -0,0 +1,16 @@ +package roomescape.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import roomescape.template.ApiResponseTemplate; + +@ControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(BadRequestException.class) + public ResponseEntity> handleBadRequestException(BadRequestException e) { + ApiResponseTemplate response = new ApiResponseTemplate<>("error", e.getMessage()); + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index f19314175..e05a8bb68 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -79,4 +79,26 @@ class MissionStepTest { .statusCode(200) .body("data.size()", is(0)); } + + @Test + void 사단계() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", ""); + params.put("time", ""); + + // 필요한 인자가 없는 경우 + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(400); + + // 삭제할 예약이 없는 경우 + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(400); + } }