Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ 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'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}

test {
Expand Down
69 changes: 69 additions & 0 deletions src/main/java/roomescape/controller/ReservationController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package roomescape.controller;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import roomescape.dto.ReservationAddReq;
import roomescape.dto.ReservationReq;
import roomescape.exception.InvalidRequestReservationException;
import roomescape.exception.NotFoundReservationException;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

@Controller
public class ReservationController {

//AtomicLong: Long 자료형 가지는 Wrapping 클래스. incrementAndGet(): ++x
private AtomicLong index = new AtomicLong(0);
private List<ReservationReq> reservations = new ArrayList<>();

@GetMapping("/reservation")
public String reservation(Model model){

model.addAttribute(reservations);
return "reservation";
}

//예약 조회
@GetMapping("/reservations")
@ResponseBody
public List<ReservationReq> reservations(){

return reservations;
}

//예약 추가
@PostMapping("/reservations")
@ResponseBody
@ResponseStatus(HttpStatus.CREATED)
public ReservationReq addReservation(@RequestBody ReservationAddReq reservation, HttpServletResponse response){
if(reservation.getName().isEmpty()||reservation.getDate().isEmpty()||reservation.getTime().isEmpty()){
throw new InvalidRequestReservationException("필요한 인자가 없습니다.");
}
ReservationReq newReservation =new ReservationReq(index.incrementAndGet(),reservation.getName(),reservation.getDate(),reservation.getTime());
reservations.add(newReservation);

// ResponseEntity 사용하면 header 명시적으로 지정하지 않아도 된다고 한다.
response.setHeader("Location", "/reservations/" + newReservation.getId());
return newReservation;
}

//예약 삭제
@DeleteMapping("/reservations/{id}")
@ResponseBody
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteReservation(@PathVariable Long id){
ReservationReq delete=reservations.stream().filter(ReservationReq -> id.equals(ReservationReq.getId())).findAny().orElse(null);
if(delete==null){
throw new NotFoundReservationException("삭제할 예약이 없습니다");
}
reservations.remove(delete);
}


}
18 changes: 18 additions & 0 deletions src/main/java/roomescape/controller/StartController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package roomescape.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class StartController {

@GetMapping("/")
public String home(){
return "home";
}




}
12 changes: 12 additions & 0 deletions src/main/java/roomescape/dto/ReservationAddReq.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package roomescape.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class ReservationAddReq {
private String name;
private String date; //우선 String으로.. 차후 Date로 형변환 필요하면 교체..
private String time; //동일!!
}
17 changes: 17 additions & 0 deletions src/main/java/roomescape/dto/ReservationReq.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package roomescape.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor //기본 생성자 자동 생성
public class ReservationReq {
//캡슐화를 위해서는 private으로 해야 하지만, step12의 코드에서 private으로 하면 JSON이 접근을 못 해서 직렬화 못 하는 에러 발생
//해결을 위해 lombok의 getter 추가
private Long id;
private String name;
private String date; //우선 String으로.. 차후 Date로 형변환 필요하면 교체..
private String time; //동일!!


}
21 changes: 21 additions & 0 deletions src/main/java/roomescape/exception/ExceptionHandlers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package roomescape.exception;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;


@ControllerAdvice
public class ExceptionHandlers {

@ExceptionHandler(NotFoundReservationException.class)
public ResponseEntity NotFoundReservationException() { //딱히 메시지에 id 등 추가할 필요를 못 느껴서 매개변수 없앰.
return ResponseEntity.badRequest().build();
}

@ExceptionHandler(InvalidRequestReservationException.class)
public ResponseEntity InvalidRequestReservationException(){
return ResponseEntity.badRequest().build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package roomescape.exception;

public class InvalidRequestReservationException extends RuntimeException {
public InvalidRequestReservationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package roomescape.exception;

public class NotFoundReservationException extends RuntimeException {
public NotFoundReservationException(String message) {
super(message);
}
}
76 changes: 76 additions & 0 deletions src/test/java/roomescape/MissionStepTest.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package roomescape;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
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 {
Expand All @@ -16,4 +22,74 @@ 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(0)); // 아직 생성 요청이 없으니 Controller에서 임의로 넣어준 Reservation 갯수(=1) 만큼 검증.
}

@Test
void 삼단계() {
Map<String, String> 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<String, String> 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);
}
}