diff --git a/build.gradle b/build.gradle index ad408336d..02411de74 100644 --- a/build.gradle +++ b/build.gradle @@ -18,9 +18,12 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' + + runtimeOnly 'com.h2database:h2' } test { diff --git a/et --hard 5e335e0b40bec777c2c887a4361dd6d0be5d1f0d b/et --hard 5e335e0b40bec777c2c887a4361dd6d0be5d1f0d new file mode 100644 index 000000000..295ea4583 --- /dev/null +++ b/et --hard 5e335e0b40bec777c2c887a4361dd6d0be5d1f0d @@ -0,0 +1,71 @@ +commit db4464b2ee2776fc1a7e2c33ac06db51dca35c96 (HEAD -> roomescape-taewoo, origin/roomescape-taewoo) +Author: 김태우 <162796810+tae-wooo@users.noreply.github.com> +Date: Sun Nov 9 04:45:22 2025 +0900 + + Reservation 클래스 record 타입으로 전환 + +commit 5e335e0b40bec777c2c887a4361dd6d0be5d1f0d +Author: 김태우 <162796810+tae-wooo@users.noreply.github.com> +Date: Sun Nov 9 04:29:07 2025 +0900 + + dependencies scope 별 그룹 정리 + +commit e81315342c391df2ce1b226f975f500dca48c502 +Author: taewoo +Date: Thu Nov 6 16:17:56 2025 +0900 + + README 프로젝트 구조 추가 + +commit 65a2b867e22ee4e1c972ae64fb560d3069e2d329 +Author: taewoo +Date: Thu Nov 6 15:54:16 2025 +0900 + + README파일 추가 + +commit db298e1d1f6dc6fbaa4eb3e5644e8671a1b76e1a +Author: taewoo +Date: Thu Nov 6 06:35:18 2025 +0900 + + 1,2단계 테스트 코드 + +commit 6cc4f11270dbd5587e645d58012541d95b7e8e3a +Author: taewoo +Date: Thu Nov 6 06:34:50 2025 +0900 + + 예약 페이지 응답 및 예약 목록 조회 기능 구현 + +commit 0599c6420f34983907795296d8e5bc82db75ea61 +Author: taewoo +Date: Thu Nov 6 06:33:49 2025 +0900 + + 예약 조회 기능 테스트를 위한 기본 Reservation 데이터 정의 + +commit 84a59b597b453e7c1b9768671b8f552aa0820eb2 +Author: taewoo +Date: Thu Nov 6 04:11:06 2025 +0900 + + index.html 파일명 규칙 대신 / 매핑을 통해 home.html 을 초기화면으로 반환하도록 구현 + +commit 4ee151079690731ed3b64b3d94a513ed8580dc8c +Author: taewoo +Date: Thu Nov 6 04:09:41 2025 +0900 + + web 기능 및 thymeleaf 템플릿 엔진 의존성 추가 + +commit 13c25ffbfd35945a01ec7efd07f0e56638ca0082 (origin/zinyan, origin/wzrabbit, origin/wfs0502, origin/wateralsie, origin/uoehisx, origin/taeyeonroyce, origin/tae-wooo, origin/stonecau, origin/sseung3424, origin/sohvun, origin/shin-mallang, origin/programming-alpaca, origin/nyeroni, origin/nova0128, origin/nonactress, origin/newvh, origin/mintcoke123, origin/marriedsenior, origin/main, origin/leeyoonjoo, origin/leegwichan, origin/kyy00n, origin/kyuwon-choi, origin/kwakminu, origin/kokodak, origin/kimsky247-coder, origin/ke-62, origin/kang1221, origin/jxxxxxn, origin/jihyeonanan, origin/jelee2555, origin/idle2534, origin/idealhyun, origin/hong-sile, origin/gy102912, origin/fanngineer, origin/corjqnrl, origin/chemistryx, origin/breadquokka, origin/boyekim, origin/bingle625, origin/bbggr1209, origin/HEAD, origin/8parks, main) +Author: boorownie +Date: Tue Aug 15 01:12:26 2023 +0900 + + default + +commit 94aebe2cc437605d73f15fc851e9504a976c3c37 +Author: boorownie +Date: Tue Aug 15 01:12:02 2023 +0900 + + init + +commit ef8a7256668457b8b9be3685163a937c2b1e6070 +Author: 류성현 <4353846+boorownie@users.noreply.github.com> +Date: Tue Aug 15 01:16:06 2023 +0900 + + Initial commit diff --git a/src/README.md b/src/README.md index 7d83b8151..1feec199d 100644 --- a/src/README.md +++ b/src/README.md @@ -11,7 +11,6 @@ - /reservation 요청 시 예약 관리 페이지가 응답할 수 있도록 구현 - /reservations 요청 시 예약 목록을 JSON 데이터로 조회할 수 있도록 구현 - ## step 3 예약 추가 / 취소 - API 명세를 따라 예약 추가 API 와 삭제 API를 구현 @@ -22,6 +21,19 @@ - 예약 관련 API 호출 시 에러가 발생하는 경우 중 요청의 문제인 경우 Status Code를 400으로 응답 예를 들면 예약 추가 시 필요한 인자값이 비어있는 경우 혹은 삭제 할 예약의 식별자로 저장된 예약을 찾을 수 없는 경우가 있다 +## step 5 데이터베이스 적용하기 + +- h2 데이터베이스를 활용하여 데이터를 저장하도록 수정 + +## step 6 + +- 예약 조회 API 처리 로직에서 저장된 예약을 조회할 때 데이터베이스를 활용 + +## step 7 + +- 예약 추가/취소 API 처리 로직에서 데이터베이스를 활용하도록 수정 + - 기존에 사용하던 List 및 AtomicLong 을 제거 +- 예약 관리 기능이 정상 동작하도록 기능을 완성 --- @@ -29,14 +41,13 @@ ### Reservation - - 예약 정보를 표현하는 도메인 객체 - 생성 시 이름/날짜/시간 검증을 수행하여 유효한 예약만 생성되도록 보장 - +- DB에서 조회한 예약은 newReservationFromDb 로 생성 +- 신규 예약은 createReservation 로 생성하여 검증 포함 ### ReservationController -- /reservation 요청 시 reservation.html 응답 - /reservations 요청 시 예약 목록 조회 API 응답(JSON) - /reservations POST 요청에서 예약 추가 - /reservations/{id} DELETE 요청에서 예약 삭제 @@ -46,19 +57,38 @@ - Entry Point -### RoomescapeController +### PageController - / 요청 시 home.html 응답 +- /reservation 요청시 reservation.html 응답 +### Reservation Service + +- 도메인 객체 생성 +- DAO를 통해 예약 추가·조회·삭제 처리 +- 삭제 시 영향받은 row 수를 확인하여, 없으면 NotFoundReservationException 발생 + +### RerservationDao + +- KeyHolder 를 사용해 예약 추가 시 생성된 ID 반환 +- 전체 예약 조회/삭제 기능 구현 + ### ReservationRequest - 예약 생성 요청을 받을 때 사용하는 DTO +- name/date/time 값에 대해 NotBlank 검증 수행 + +### ReservationResponse + +- Reservation 도메인을 응답 형태로 변환하는 DTO + ### GlobalExceptionHandler - 예약 생성/삭제 시 발생하는 예외를 처리 - 잘못된 요청에 대해 400 응답 반환 +- DB 관련 예외(DataAccessException)는 500으로 내부 서버 오류 처리 --- diff --git a/src/main/java/roomescape/Reservation.java b/src/main/java/roomescape/Reservation.java deleted file mode 100644 index 69a34ea40..000000000 --- a/src/main/java/roomescape/Reservation.java +++ /dev/null @@ -1,31 +0,0 @@ -package roomescape; - -public class Reservation { - private Long id; - private String name; - private String date; - private String time; - - public Reservation(Long id, String name, String 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 String getDate() { - return date; - } - - public String getTime() { - return time; - } -} diff --git a/src/main/java/roomescape/ReservationController.java b/src/main/java/roomescape/ReservationController.java deleted file mode 100644 index e532ec317..000000000 --- a/src/main/java/roomescape/ReservationController.java +++ /dev/null @@ -1,32 +0,0 @@ -package roomescape; - -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -import java.util.ArrayList; -import java.util.List; - -@Controller -public class ReservationController { - private List reservations = new ArrayList<>(); - - public ReservationController() { - reservations.add(new Reservation(1L, "브라운", "2023-01-01", "10:00")); - reservations.add(new Reservation(2L, "브라운", "2023-01-02", "11:00")); - reservations.add(new Reservation(3L, "브라운", "2023-01-03", "13:00")); - } - - @GetMapping("/reservation") - public String reservation() { - return "reservation"; - } - - @GetMapping("/reservations") - public ResponseEntity> read() { - ; - return ResponseEntity - .ok() - .body(reservations); - } -} diff --git a/src/main/java/roomescape/RoomescapeController.java b/src/main/java/roomescape/RoomescapeController.java deleted file mode 100644 index c6b465f5e..000000000 --- a/src/main/java/roomescape/RoomescapeController.java +++ /dev/null @@ -1,12 +0,0 @@ -package roomescape; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class RoomescapeController { - @GetMapping("/") - public String home() { - return "home"; - } -} diff --git a/src/main/java/roomescape/controller/ReservationController.java b/src/main/java/roomescape/controller/ReservationController.java index 83ce518cc..be3a76ae6 100644 --- a/src/main/java/roomescape/controller/ReservationController.java +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -11,24 +11,26 @@ import roomescape.domain.Reservation; import roomescape.dto.ReservationRequest; import roomescape.dto.ReservationResponse; -import roomescape.exception.NotFoundReservationException; + +import roomescape.service.ReservationService; import java.net.URI; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; @RestController public class ReservationController { - private final List reservations = new ArrayList<>(); - private final AtomicLong index = new AtomicLong(1); + + private final ReservationService reservationService; + + public ReservationController(ReservationService reservationService) { + this.reservationService = reservationService; + } @PostMapping("/reservations") public ResponseEntity createReservations(@Valid @RequestBody ReservationRequest request) { - Reservation newReservation = Reservation.createReservation(index.getAndIncrement(), request.name(), request.date(), request.time()); - reservations.add(newReservation); + Reservation newReservation = reservationService.registerReservation(request.name(), request.date(), request.time()); + return ResponseEntity.created(URI.create("/reservations/" + newReservation.getId())). body(ReservationResponse.from(newReservation)); } @@ -36,8 +38,11 @@ public ResponseEntity createReservations(@Valid @RequestBod @GetMapping("/reservations") public ResponseEntity> readReservations() { + List reservations = reservationService.getReservations(); + List responses = reservations.stream(). map(ReservationResponse::from).toList(); + return ResponseEntity .ok() .body(responses); @@ -45,13 +50,7 @@ public ResponseEntity> readReservations() { @DeleteMapping("/reservations/{id}") public ResponseEntity deleteReservations(@PathVariable Long id) { - Reservation reservation = reservations.stream() - .filter(it -> Objects.equals(it.getId(), id)) - .findFirst() - .orElseThrow(() -> new NotFoundReservationException("예약된 기록이 없습니다.")); - - reservations.remove(reservation); - + reservationService.delete(id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/roomescape/dao/ReservationDao.java b/src/main/java/roomescape/dao/ReservationDao.java new file mode 100644 index 000000000..8ecb20a89 --- /dev/null +++ b/src/main/java/roomescape/dao/ReservationDao.java @@ -0,0 +1,52 @@ +package roomescape.dao; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.simple.SimpleJdbcInsert; +import org.springframework.stereotype.Repository; +import roomescape.domain.Reservation; + +import java.util.List; +import java.util.Map; + +@Repository +public class ReservationDao { + + private final JdbcTemplate jdbcTemplate; + + public ReservationDao(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public Long insert(Reservation reservation) { + SimpleJdbcInsert insertActor = new SimpleJdbcInsert(jdbcTemplate) + .withTableName("reservation") + .usingGeneratedKeyColumns("id"); + Map parameters = Map.of( + "name", reservation.getName(), + "date", reservation.getDate(), + "time", reservation.getTime() + ); + Number id = insertActor.executeAndReturnKey(parameters); + return id.longValue(); + } + + public List findAll() { + String sql = "select id,name,date,time from reservation"; + return jdbcTemplate.query(sql, + (resultSet, rowNum) -> { + Reservation reservation = Reservation.newReservationFromDb( + resultSet.getLong("id"), + resultSet.getString("name"), + resultSet.getString("date"), + resultSet.getString("time") + + ); + return reservation; + }); + } + + + public int delete(Long id) { + return jdbcTemplate.update("delete from reservation where id = ?", id); + } +} diff --git a/src/main/java/roomescape/domain/Reservation.java b/src/main/java/roomescape/domain/Reservation.java index 52f629d24..dc9f8cbaf 100644 --- a/src/main/java/roomescape/domain/Reservation.java +++ b/src/main/java/roomescape/domain/Reservation.java @@ -1,5 +1,6 @@ package roomescape.domain; +import roomescape.exception.FailMessage; import roomescape.exception.InvalidReservationArgumentException; import java.time.LocalDate; @@ -8,28 +9,35 @@ public class Reservation { private final Long id; private final String name; - private final LocalDate date; - private final LocalTime time; + private final String date; + private final String time; + + private Reservation(Long id, String name, String date, String time) { - private Reservation(Long id, String name, LocalDate date, LocalTime time) { this.id = id; this.name = name; this.date = date; this.time = time; } + public static Reservation newReservationFromDb(Long id, String name, String date, String time) { + return new Reservation(id, name, date, time); + } + public static Reservation createReservation(Long id, String name, String stringDate, String stringTime) { LocalDate date = LocalDate.parse(stringDate); LocalTime time = LocalTime.parse(stringTime); if (date.isBefore(LocalDate.now())) { - throw new InvalidReservationArgumentException("예약 날짜는 오늘 이후여야 합니다."); + throw new InvalidReservationArgumentException(FailMessage.BAD_REQUEST); } if (date.isEqual(LocalDate.now()) && time.isBefore(LocalTime.now())) { - throw new InvalidReservationArgumentException("예약 시간은 현재 시간 이후여야 합니다."); + throw new InvalidReservationArgumentException(FailMessage.BAD_REQUEST); } - return new Reservation(id, name, date, time); + + return new Reservation(id, name, stringDate, stringTime); + } @@ -41,11 +49,12 @@ public String getName() { return name; } - public LocalDate getDate() { + public String getDate() { return date; } - public LocalTime getTime() { + public String getTime() { + return time; } } diff --git a/src/main/java/roomescape/dto/ErrorResponse.java b/src/main/java/roomescape/dto/ErrorResponse.java index a218a7395..cdc6f43f7 100644 --- a/src/main/java/roomescape/dto/ErrorResponse.java +++ b/src/main/java/roomescape/dto/ErrorResponse.java @@ -1,4 +1,4 @@ package roomescape.dto; -public record ErrorResponse(String message) { +public record ErrorResponse(int code, String message) { } diff --git a/src/main/java/roomescape/dto/ReservationResponse.java b/src/main/java/roomescape/dto/ReservationResponse.java index a6768ddf3..d1898c4e6 100644 --- a/src/main/java/roomescape/dto/ReservationResponse.java +++ b/src/main/java/roomescape/dto/ReservationResponse.java @@ -3,10 +3,9 @@ import roomescape.domain.Reservation; -import java.time.LocalDate; -import java.time.LocalTime; -public record ReservationResponse(Long id, String name, LocalDate date, LocalTime time) { +public record ReservationResponse(Long id, String name, String date, String time) { + public static ReservationResponse from(Reservation reservation) { return new ReservationResponse(reservation.getId(), reservation.getName(), reservation.getDate(), reservation.getTime()); } diff --git a/src/main/java/roomescape/exception/FailMessage.java b/src/main/java/roomescape/exception/FailMessage.java new file mode 100644 index 000000000..eadc95653 --- /dev/null +++ b/src/main/java/roomescape/exception/FailMessage.java @@ -0,0 +1,31 @@ +package roomescape.exception; + +import org.springframework.http.HttpStatus; + +public enum FailMessage { + //400 + BAD_REQUEST(HttpStatus.BAD_REQUEST, 40000, "잘못된 요청입니다."), + DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 50000, "DB 처리 중 오류가 발생했습니다."); + + private final HttpStatus httpStatus; + private final int code; + private final String message; + + FailMessage(HttpStatus httpStatus, int code, String message) { + this.httpStatus = httpStatus; + this.code = code; + this.message = message; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public String getMessage() { + return message; + } + + public int getCode() { + return code; + } +} diff --git a/src/main/java/roomescape/exception/GlobalExceptionHandler.java b/src/main/java/roomescape/exception/GlobalExceptionHandler.java index 6e6b40ffe..fe1f100e1 100644 --- a/src/main/java/roomescape/exception/GlobalExceptionHandler.java +++ b/src/main/java/roomescape/exception/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package roomescape.exception; +import org.springframework.dao.DataAccessException; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -13,31 +14,36 @@ public class GlobalExceptionHandler { @ExceptionHandler(InvalidReservationArgumentException.class) - public ResponseEntity handlerInvalidReservationArgumentException(InvalidReservationArgumentException e) { - return ResponseEntity - .badRequest() - .body(new ErrorResponse(e.getMessage())); + public ResponseEntity handleInvalidReservationArgumentException(InvalidReservationArgumentException e) { + return buildErrorResponse(e.getFailMessage()); } @ExceptionHandler(NotFoundReservationException.class) - public ResponseEntity handlerNotFoundReservationException(NotFoundReservationException e) { - return ResponseEntity - .badRequest() - .body(new ErrorResponse(e.getMessage())); + public ResponseEntity handleNotFoundReservationException(NotFoundReservationException e) { + return buildErrorResponse(e.getFailMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handlerMethodArgumentNotValidException(MethodArgumentNotValidException e) { - String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); - return ResponseEntity - .badRequest() - .body(new ErrorResponse(message)); + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + FailMessage fail = FailMessage.BAD_REQUEST; + return buildErrorResponse(fail); } @ExceptionHandler(DateTimeParseException.class) public ResponseEntity handleDateTimeParseException(DateTimeParseException e) { - return ResponseEntity - .badRequest() - .body(new ErrorResponse("날짜 또는 시간이 기본형식이 아닙니다. ex) 2025-11-16, 18:23")); + FailMessage fail = FailMessage.BAD_REQUEST; + return buildErrorResponse(fail); + } + + @ExceptionHandler(DataAccessException.class) + public ResponseEntity handleDBException(DataAccessException e) { + FailMessage fail = FailMessage.DATABASE_ERROR; + return buildErrorResponse(fail); + } + + + private ResponseEntity buildErrorResponse(FailMessage failMessage) { + return ResponseEntity.status(failMessage.getHttpStatus()) + .body(new ErrorResponse(failMessage.getCode(), failMessage.getMessage())); } } diff --git a/src/main/java/roomescape/exception/InvalidReservationArgumentException.java b/src/main/java/roomescape/exception/InvalidReservationArgumentException.java index 89ae99821..a70bcb501 100644 --- a/src/main/java/roomescape/exception/InvalidReservationArgumentException.java +++ b/src/main/java/roomescape/exception/InvalidReservationArgumentException.java @@ -1,7 +1,14 @@ package roomescape.exception; public class InvalidReservationArgumentException extends RuntimeException { - public InvalidReservationArgumentException(String message) { - super(message); + private final FailMessage failMessage; + + public InvalidReservationArgumentException(FailMessage failMessage) { + super(failMessage.getMessage()); + this.failMessage = failMessage; + } + + public FailMessage getFailMessage() { + return failMessage; } } diff --git a/src/main/java/roomescape/exception/NotFoundReservationException.java b/src/main/java/roomescape/exception/NotFoundReservationException.java index 55990b696..e8c8dc219 100644 --- a/src/main/java/roomescape/exception/NotFoundReservationException.java +++ b/src/main/java/roomescape/exception/NotFoundReservationException.java @@ -1,7 +1,14 @@ package roomescape.exception; public class NotFoundReservationException extends RuntimeException { - public NotFoundReservationException(String message) { - super(message); + private final FailMessage failMessage; + + public NotFoundReservationException(FailMessage failMessage) { + super(failMessage.getMessage()); + this.failMessage = failMessage; + } + + public FailMessage getFailMessage() { + return failMessage; } } diff --git a/src/main/java/roomescape/service/ReservationService.java b/src/main/java/roomescape/service/ReservationService.java new file mode 100644 index 000000000..2e7b78137 --- /dev/null +++ b/src/main/java/roomescape/service/ReservationService.java @@ -0,0 +1,38 @@ +package roomescape.service; + +import org.springframework.stereotype.Service; +import roomescape.dao.ReservationDao; +import roomescape.domain.Reservation; +import roomescape.exception.FailMessage; +import roomescape.exception.NotFoundReservationException; + +import java.util.List; + +@Service +public class ReservationService { + + private final ReservationDao reservationDao; + + public ReservationService(ReservationDao reservationDao) { + this.reservationDao = reservationDao; + } + + public Reservation registerReservation(String name, String date, String time) { + Reservation reservation = Reservation.createReservation(null, name, date, time); + Long id = reservationDao.insert(reservation); + return Reservation.newReservationFromDb(id, name, date, time); + } + + public List getReservations() { + return reservationDao.findAll(); + } + + + public void delete(Long id) { + int result = reservationDao.delete(id); + + if (result == 0) { + throw new NotFoundReservationException(FailMessage.BAD_REQUEST); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e69de29bb..b63619d73 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console +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..875cb6161 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,9 @@ + +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) +); diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index be935e0b3..36dec922f 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -3,14 +3,21 @@ import io.restassured.RestAssured; import io.restassured.http.ContentType; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.annotation.DirtiesContext; +import roomescape.domain.Reservation; +import java.sql.Connection; +import java.sql.SQLException; import java.util.HashMap; +import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.core.Is.is; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @@ -94,4 +101,60 @@ public class MissionStepTest { .then().log().all() .statusCode(400); } + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + void 오단계() { + try (Connection connection = jdbcTemplate.getDataSource().getConnection()) { + assertThat(connection).isNotNull(); + assertThat(connection.getCatalog()).isEqualTo("DATABASE"); + assertThat(connection.getMetaData().getTables(null, null, "RESERVATION", null).next()).isTrue(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Test + void 육단계() { + jdbcTemplate.update("INSERT INTO reservation (name, date, time) VALUES (?, ?, ?)", "브라운", "2023-08-05", "15:40"); + + List reservations = RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200).extract() + .jsonPath().getList(".", Reservation.class); + + Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + + assertThat(reservations.size()).isEqualTo(count); + } + + @Test + void 칠단계() { + Map params = new HashMap<>(); + params.put("name", "브라운"); + params.put("date", "2026-08-05"); + params.put("time", "10:00"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/reservations") + .then().log().all() + .statusCode(201) + .header("Location", "/reservations/1"); + + Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + assertThat(count).isEqualTo(1); + + RestAssured.given().log().all() + .when().delete("/reservations/1") + .then().log().all() + .statusCode(204); + + Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class); + assertThat(countAfterDelete).isEqualTo(0); + } }