diff --git a/build.gradle b/build.gradle index 57267157c..15658a26c 100644 --- a/build.gradle +++ b/build.gradle @@ -14,8 +14,13 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + runtimeOnly 'com.h2database:h2' + } test { diff --git a/src/main/java/roomescape/RoomescapeController.java b/src/main/java/roomescape/RoomescapeController.java new file mode 100644 index 000000000..35aed308e --- /dev/null +++ b/src/main/java/roomescape/RoomescapeController.java @@ -0,0 +1,12 @@ +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 new file mode 100644 index 000000000..0bfd7c2a4 --- /dev/null +++ b/src/main/java/roomescape/controller/ReservationController.java @@ -0,0 +1,94 @@ +// ReservationController.java +package roomescape.controller; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.jdbc.core.JdbcTemplate; + +import roomescape.model.Reservation; +import roomescape.exception.BadRequestReservationException; + +import javax.sql.DataSource; +import java.util.*; + +@RestController // RestController로 변경 +@RequestMapping("/reservations") +public class ReservationController { + private JdbcTemplate jdbcTemplate; + + public ReservationController(DataSource dataSource, JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @GetMapping + public List getReservation() { + String sql = "select id, name, date, time from reservation"; + List reservations = jdbcTemplate.query( + sql, (resultSet, rowNum) -> { + Reservation reservation = new Reservation( + resultSet.getLong("id"), + resultSet.getString("name"), + resultSet.getString("date"), + resultSet.getString("time") + ); + return reservation; + } + ); + return reservations; + } + + @PostMapping + public ResponseEntity addReservation(@RequestBody Reservation newReservation) { + // 예외 조건 추가 + if (newReservation.getName() == null || newReservation.getName().isBlank() || + newReservation.getDate() == null || newReservation.getDate().isBlank() || + newReservation.getTime() == null || newReservation.getTime().isBlank()) { + throw new BadRequestReservationException("Required fields are missing."); + } + + String sql = "INSERT INTO reservation(name, date, time) VALUES (?, ?, ?)"; + + int insertCount = jdbcTemplate.update( + sql, + newReservation.getName(), + newReservation.getDate(), + newReservation.getTime() + ); + + if (insertCount <= 0) { + throw new RuntimeException("Failed to insert reservation"); + } + + Long generatedId = jdbcTemplate.queryForObject("SELECT MAX(id) FROM reservation", Long.class); + + Reservation reservation = new Reservation( + generatedId, + newReservation.getName(), + newReservation.getDate(), + newReservation.getTime() + ); + + return ResponseEntity.status(HttpStatus.CREATED) + .header("Location", "/reservations/" + reservation.getId()) + .body(reservation); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteReservation(@PathVariable Long id) { + int deletedCount = jdbcTemplate.update("DELETE FROM reservation WHERE id = ?", id); if (deletedCount > 0) { + return ResponseEntity.noContent().build(); + } else { + throw new BadRequestReservationException("Reservation not found."); + } + } +} diff --git a/src/main/java/roomescape/exception/BadRequestReservationException.java b/src/main/java/roomescape/exception/BadRequestReservationException.java new file mode 100644 index 000000000..4eeba19f1 --- /dev/null +++ b/src/main/java/roomescape/exception/BadRequestReservationException.java @@ -0,0 +1,7 @@ +package roomescape.exception; + +public class BadRequestReservationException extends RuntimeException { + public BadRequestReservationException(String message) { + super(message); + } +} diff --git a/src/main/java/roomescape/exception/ReservationExceptionHandler.java b/src/main/java/roomescape/exception/ReservationExceptionHandler.java new file mode 100644 index 000000000..9e4e36ef6 --- /dev/null +++ b/src/main/java/roomescape/exception/ReservationExceptionHandler.java @@ -0,0 +1,13 @@ +package roomescape.exception; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ReservationExceptionHandler { + @ExceptionHandler(BadRequestReservationException.class) + public ResponseEntity handleBadRequest(BadRequestReservationException e) { + return ResponseEntity.badRequest().build(); + } +} diff --git a/src/main/java/roomescape/model/Reservation.java b/src/main/java/roomescape/model/Reservation.java new file mode 100644 index 000000000..b08b3b2a2 --- /dev/null +++ b/src/main/java/roomescape/model/Reservation.java @@ -0,0 +1,31 @@ +package roomescape.model; + +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/resources/application.properties b/src/main/resources/application.properties index e69de29bb..e26604d31 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -0,0 +1,4 @@ +# h2-console 활성화 여부 +spring.h2.console.enabled=true +#db url +spring.datasource.url=jdbc:h2:mem:database \ No newline at end of file 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) +); diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index cf4efbe91..0f16d9127 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -1,9 +1,23 @@ package roomescape; -import io.restassured.RestAssured; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.is; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import roomescape.model.Reservation; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) @@ -16,4 +30,132 @@ 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(2)); // 아직 생성 요청이 없으니 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("id", is(1)); + + RestAssured.given().log().all() + .when().get("/reservations") + .then().log().all() + .statusCode(200) + .body("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("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); + } + + @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", "2023-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); + } + + }