diff --git a/api/src/main/java/org/onewayticket/controller/AuthController.java b/api/src/main/java/org/onewayticket/controller/AuthController.java deleted file mode 100644 index 6c7b825..0000000 --- a/api/src/main/java/org/onewayticket/controller/AuthController.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.onewayticket.controller; - -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/") -public class AuthController { - -} diff --git a/api/src/main/java/org/onewayticket/controller/BookingController.java b/api/src/main/java/org/onewayticket/controller/BookingController.java index 34b680c..ec17a44 100644 --- a/api/src/main/java/org/onewayticket/controller/BookingController.java +++ b/api/src/main/java/org/onewayticket/controller/BookingController.java @@ -2,75 +2,91 @@ import lombok.RequiredArgsConstructor; import org.onewayticket.dto.BookingDetailsDto; -import org.onewayticket.dto.BookingDto; import org.onewayticket.dto.BookingRequestDto; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +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.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal; import java.time.LocalDate; -import java.util.List; +import java.time.format.DateTimeParseException; @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/bookings") public class BookingController { + // 예약 생성 @PostMapping - public ResponseEntity createBooking(@RequestBody BookingRequestDto bookingRequestInfo) { - if (bookingRequestInfo.paymentId() == null || bookingRequestInfo.paymentId().isBlank()) { - return ResponseEntity.badRequest().body("Payment information is missing"); + public ResponseEntity createBooking(@RequestBody BookingRequestDto bookingRequestInfo) { + if (bookingRequestInfo.paymentId() == null || bookingRequestInfo.paymentId().isBlank() || !bookingRequestInfo.paymentId().equals("Confirmed")) { + return ResponseEntity.status(400).build(); } - return ResponseEntity.ok("Booking created"); + + return ResponseEntity.ok(new BookingDetailsDto("A1234", bookingRequestInfo.bookingName(), bookingRequestInfo.bookingEmail(), bookingRequestInfo.bookingPhoneNumber(), + bookingRequestInfo.flightId(), "ICN", "NRT", LocalDate.of(2023, 12, 1), LocalDate.of(2023, 12, 1), "Jane Doe", + LocalDate.parse(bookingRequestInfo.birthDate()), 25, "Female", "AB123456", "Korean", "12A", "Economy", BigDecimal.valueOf(500), "Confirmed")); } - // 예약 상세 조회 - @GetMapping("/{id}") - public ResponseEntity getBookingDetails(@PathVariable String id) { + @GetMapping + public ResponseEntity getBookingDetails( + @RequestParam("bookingId") String bookingId, + @RequestParam("name") String name, + @RequestParam("birthDate") String birthDate) { + + // 날짜 유효성 검증 + if (!isValidDate(birthDate)) { + return ResponseEntity.badRequest().body(null); + } + + // 미리 정의된 예약 정보 BookingDetailsDto bookingDetails = new BookingDetailsDto( - id, "John Doe", "john.doe@example.com", "123456789", + "B1234", "John Doe", "john.doe@example.com", "123456789", "FL123", "ICN", "NRT", LocalDate.of(2023, 12, 1), LocalDate.of(2023, 12, 1), - "Jane Doe", 25, "Female", "AB123456", "Korean", "12A", "Economy", - BigDecimal.valueOf(500), "Confirmed" - ); + name, LocalDate.parse(birthDate), 25, "Female", "AB123456", "Korean", "12A", "Economy", + BigDecimal.valueOf(500), "Confirmed"); + + // bookingId가 일치하지 않을 경우 예외 처리 + if (!bookingDetails.bookingId().equals(bookingId)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); + } + return ResponseEntity.ok(bookingDetails); } + // 예약 취소 @DeleteMapping("/{id}") - public ResponseEntity cancelBooking(@PathVariable String id, @RequestHeader("Authorization") String authHeader) { + public ResponseEntity cancelBooking( + @PathVariable String id, + @RequestHeader(value = "Authorization", required = false) String authHeader) { + // 토큰 검증 - if (!authHeader.equals("Bearer VALID_TOKEN")) { - return ResponseEntity.status(403).body("Unauthorized user"); + if (authHeader == null || !authHeader.equals("Bearer VALID_TOKEN")) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); // 메시지 없이 401 반환 } - return ResponseEntity.ok("Booking canceled"); + // 200 OK와 함께 예약 ID 반환 + return ResponseEntity.ok("Booking with ID " + id + " has been canceled successfully."); } - @GetMapping("/my") - public ResponseEntity> getMyBookings( - @RequestParam("bookingId") String bookingId, - @RequestParam("name") String name, - @RequestParam("birthDate") String birthDate - ) { - // 더미 데이터 - List bookings = List.of( - new BookingDto("BK001", "John Doe", "john.doe@example.com", "123456789"), - new BookingDto("BK002", "Jane Smith", "jane.smith@example.com", "987654321") - ); - - // 사용자 정보로 필터링 - List filteredBookings = bookings.stream() - .filter(booking -> booking.bookingId().equals(bookingId) && booking.reservationName().equalsIgnoreCase(name)) - .toList(); - - // 조건에 따라 응답 처리 - if (filteredBookings.isEmpty()) { - return ResponseEntity.status(404).build(); // 404 반환 + // 오늘 이후 날짜이거나 입력 값이 맞는지 확인 + private boolean isValidDate(String date) { + try { + LocalDate parsedDate = LocalDate.parse(date); // 기본 포맷 yyyy-MM-dd + return !parsedDate.isAfter(LocalDate.now()); // 오늘 이후 날짜인지 확인 + } catch (DateTimeParseException e) { + return false; // 형식이 잘못된 경우 false 반환 } - - return ResponseEntity.ok(filteredBookings); } diff --git a/api/src/main/java/org/onewayticket/controller/FlightController.java b/api/src/main/java/org/onewayticket/controller/FlightController.java index f49e17f..ea4457d 100644 --- a/api/src/main/java/org/onewayticket/controller/FlightController.java +++ b/api/src/main/java/org/onewayticket/controller/FlightController.java @@ -50,28 +50,53 @@ public ResponseEntity> getCheapestFlights() { .toList()); } + // 검색결과 보여줄 필터 추가 @GetMapping("/search") public ResponseEntity> searchFlights( @RequestParam("departure") String departure, @RequestParam("destination") String destination, @RequestParam("departureDate") String departureDate, - @RequestParam("numberOfPassengers") Integer numberOfPassengers) { + @RequestParam("numberOfPassengers") Integer numberOfPassengers, + @RequestParam(value = "sort", defaultValue = "price") String sort) { + // 출발지와 목적지가 같은 경우 if (departure.equalsIgnoreCase(destination)) { return ResponseEntity.badRequest().body(List.of()); } - // 예제 데이터 반환 - return ResponseEntity.ok(List.of( + // 예제 데이터 생성 + List flights = List.of( + new FlightDto("FL001", BigDecimal.valueOf(50), LocalDateTime.now(), departure, destination, + LocalDateTime.now().plusHours(2), LocalDateTime.now().plusHours(4), Duration.ofHours(2)), + new FlightDto("FL002", BigDecimal.valueOf(70), LocalDateTime.now(), departure, destination, + LocalDateTime.now().plusHours(3), LocalDateTime.now().plusHours(5), Duration.ofHours(2)), new FlightDto("FL003", BigDecimal.valueOf(120), LocalDateTime.now(), departure, destination, - LocalDateTime.now().plusHours(2), LocalDateTime.now().plusHours(4), Duration.ofHours(2)) - )); + LocalDateTime.now().plusHours(4), LocalDateTime.now().plusHours(6), Duration.ofHours(2)) + ); + + // 정렬 로직 추가 + List sortedFlights = flights.stream() + .sorted((f1, f2) -> { + switch (sort) { + case "price": + default: + return f1.price().compareTo(f2.price()); + case "arrivalTime": + return f1.arrivalTime().compareTo(f2.arrivalTime()); + case "flightDuration": + return f1.flightDuration().compareTo(f2.flightDuration()); + + } + }) + .toList(); + + return ResponseEntity.ok(sortedFlights); } @GetMapping("/{flightId}") public ResponseEntity getFlightDetails(@PathVariable String flightId) { - if (flightId.length() > 50) { // 예제: ID가 50자를 초과하면 오류 반환 + if (flightId.length() > 50) { // flightId 유효성 검사 return ResponseEntity.badRequest().build(); } diff --git a/api/src/main/java/org/onewayticket/controller/PaymentController.java b/api/src/main/java/org/onewayticket/controller/PaymentController.java deleted file mode 100644 index c769f77..0000000 --- a/api/src/main/java/org/onewayticket/controller/PaymentController.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.onewayticket.controller; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.Reader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; - -@Controller -public class PaymentController { - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - @RequestMapping(value = "/confirm") - public ResponseEntity> confirmPayment(@RequestBody String jsonBody) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); - String orderId; - String amount; - String paymentKey; - - try { - // 클라이언트에서 받은 JSON 요청 바디를 파싱합니다. - Map requestData = objectMapper.readValue(jsonBody, Map.class); - paymentKey = (String) requestData.get("paymentKey"); - orderId = (String) requestData.get("orderId"); - amount = (String) requestData.get("amount"); - } catch (Exception e) { - throw new RuntimeException("JSON 파싱 에러", e); - } - - // 요청 JSON 객체 생성 - Map requestMap = new HashMap<>(); - requestMap.put("orderId", orderId); - requestMap.put("amount", amount); - requestMap.put("paymentKey", paymentKey); - - // 토스페이먼츠 API 인증 헤더 생성 - String widgetSecretKey = "test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6"; - Base64.Encoder encoder = Base64.getEncoder(); - byte[] encodedBytes = encoder.encode((widgetSecretKey + ":").getBytes(StandardCharsets.UTF_8)); - String authorizations = "Basic " + new String(encodedBytes); - - // HTTP 연결 설정 - URL url = new URL("https://api.tosspayments.com/v1/payments/confirm"); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestProperty("Authorization", authorizations); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - - // 요청 데이터를 전송합니다. - OutputStream outputStream = connection.getOutputStream(); - outputStream.write(objectMapper.writeValueAsString(requestMap).getBytes(StandardCharsets.UTF_8)); - - int code = connection.getResponseCode(); - boolean isSuccess = code == 200; - - InputStream responseStream = isSuccess ? connection.getInputStream() : connection.getErrorStream(); - - // 응답 데이터를 처리합니다. - Reader reader = new InputStreamReader(responseStream, StandardCharsets.UTF_8); - Map responseMap = objectMapper.readValue(reader, Map.class); - responseStream.close(); - - return ResponseEntity.status(code).body(responseMap); - } -} diff --git a/api/src/main/java/org/onewayticket/dto/BookingDetailsDto.java b/api/src/main/java/org/onewayticket/dto/BookingDetailsDto.java index fdc8e79..f5e069e 100644 --- a/api/src/main/java/org/onewayticket/dto/BookingDetailsDto.java +++ b/api/src/main/java/org/onewayticket/dto/BookingDetailsDto.java @@ -6,9 +6,9 @@ public record BookingDetailsDto( // 기본 예약 정보 String bookingId, // 예약 ID - String reservationName, // 예약자 이름 - String reservationEmail, // 예약자 이메일 - String reservationPhoneNumber, // 예약자 전화번호 + String bookingName, // 예약자 이름 + String bookingEmail, // 예약자 이메일 + String bookingPhoneNumber, // 예약자 전화번호 // 항공편 정보 String flightId, // 항공편 ID @@ -19,6 +19,7 @@ public record BookingDetailsDto( // 탑승자 정보 String passengerName, // 탑승자 이름 + LocalDate passengerBirthDate, // 탑승자 생년월일 int passengerAge, // 탑승자 나이 String passengerGender, // 탑승자 성별 String passengerPassportNumber, // 여권 번호 diff --git a/api/src/main/java/org/onewayticket/dto/BookingRequestDto.java b/api/src/main/java/org/onewayticket/dto/BookingRequestDto.java index cd59479..f8003c0 100644 --- a/api/src/main/java/org/onewayticket/dto/BookingRequestDto.java +++ b/api/src/main/java/org/onewayticket/dto/BookingRequestDto.java @@ -4,8 +4,8 @@ public record BookingRequestDto( String bookingName, String bookingEmail, String bookingPhoneNumber, - String birthDate, String flightId, // 항공편명 + String birthDate, String paymentId // 결제 id ) { } diff --git a/api/src/test/java/org/onewayticket/controller/BookingControllerIntegrationTest.java b/api/src/test/java/org/onewayticket/controller/BookingControllerIntegrationTest.java new file mode 100644 index 0000000..a5ee7fc --- /dev/null +++ b/api/src/test/java/org/onewayticket/controller/BookingControllerIntegrationTest.java @@ -0,0 +1,136 @@ +package org.onewayticket.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.onewayticket.dto.BookingDetailsDto; +import org.onewayticket.dto.BookingRequestDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class BookingControllerIntegrationTest { + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + private String baseUrl; + + @BeforeEach + void setup() { + baseUrl = "http://localhost:" + port; + } + + @Test + @DisplayName("유효한 정보 입력시 예약이 완료됩니다.") + void Create_booking_with_validate_info() { + // Given + String url = baseUrl + "/api/v1/bookings"; + BookingRequestDto request = new BookingRequestDto( + "John Doe", "john.doe@example.com", "123456789", "FL123", "1995-05-01" + , "Confirmed" + ); + + // When + ResponseEntity response = restTemplate.postForEntity(url, request, BookingDetailsDto.class); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals("John Doe", response.getBody().bookingName()); + } + + @Test + @DisplayName("유효하지 않은 Payment ID 입력시 예약이 되지 않습니다.") + void Create_Booking_With_Invalid_PaymentId() { + // Given + String url = baseUrl + "/api/v1/bookings"; + BookingRequestDto request = new BookingRequestDto( + "John Doe", "john.doe@example.com", "123456789", "FL123", + "1995-05-01", "INVALID*ID" // 유효하지 않은 결제 ID + ); + + // When + ResponseEntity response = restTemplate.postForEntity(url, request, Void.class); + + // Then + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + @Test + @DisplayName("기존 예약 고객은 예약번호, 이름, 생년월일을 통해 예약정보를 조회할 수 있습니다.") + void Get_bookingDetails_with_valid_info() { + // Given + String url = baseUrl + "/api/v1/bookings?bookingId=B1234&name=John Doe&birthDate=1995-05-01"; + + // When + ResponseEntity response = restTemplate.getForEntity(url, BookingDetailsDto.class); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals("B1234", response.getBody().bookingId()); + assertEquals("John Doe", response.getBody().passengerName()); + } + + @Test + @DisplayName("존재하지 않는 예약 정보로 조회할 때는 404를 반환합니다.") + void Get_bookingDetails_with_invalid_info_not_found() { + // Given + String url = baseUrl + "/api/v1/bookings?bookingId=B9999&name=John Smith&birthDate=1980-01-01"; + + // When + ResponseEntity response = restTemplate.getForEntity(url, Void.class); + + // Then + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); + } + + @Test + @DisplayName("잘못된 입력값으로 예약 조회할 때는 400을 반환합니다.") + void Get_bookingDetails_with_invalid_input_bad_request() { + // Given + String url = baseUrl + "/api/v1/bookings?bookingId=&name=&birthDate=9999-01-01"; + + // When + ResponseEntity response = restTemplate.getForEntity(url, Void.class); + + // Then + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + @Test + @DisplayName("사용자의 정보로 생성된 유효한 토큰으로 예약을 취소할 수 있습니다.") + void Delete_booking_with_valid_token() { + // Given + String url = baseUrl + "/api/v1/bookings/123"; + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer VALID_TOKEN"); + + HttpEntity requestEntity = new HttpEntity<>(headers); + + // When + ResponseEntity response = restTemplate.exchange(url, HttpMethod.DELETE, requestEntity, String.class); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().contains("has been canceled successfully")); + } + + +} diff --git a/api/src/test/java/org/onewayticket/controller/BookingControllerTest.java b/api/src/test/java/org/onewayticket/controller/BookingControllerTest.java deleted file mode 100644 index c49d7e5..0000000 --- a/api/src/test/java/org/onewayticket/controller/BookingControllerTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.onewayticket.controller; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -class BookingControllerTest { - - @Autowired - private MockMvc mockMvc; - - // --- 예약 생성 테스트 --- - @Test - @DisplayName("예약 생성 - 결제 정보 누락 시 실패") - public void createBooking_should_Return_400_whenPaymentInfoIsMissing() throws Exception { - // Arrange - String bookingRequestWithoutPaymentId = """ - { - "bookingName": "John Doe", - "bookingEmail": "john.doe@example.com", - "bookingPhoneNumber": "123456789", - "birthDate": "1990-01-01", - "flightId": "FL123" - } - """; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/bookings") - .contentType(MediaType.APPLICATION_JSON) - .content(bookingRequestWithoutPaymentId)) - .andExpect(status().isBadRequest()) - .andExpect(content().string("Payment information is missing")); - } - - @Test - @DisplayName("예약 생성 - 성공") - public void createBooking_should_return_200_whenValidRequest() throws Exception { - // Arrange - String validBookingRequest = """ - { - "bookingName": "John Doe", - "bookingEmail": "john.doe@example.com", - "bookingPhoneNumber": "123456789", - "birthDate": "1990-01-01", - "flightId": "FL123", - "paymentId": "PAY123" - } - """; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/bookings") - .contentType(MediaType.APPLICATION_JSON) - .content(validBookingRequest)) - .andExpect(status().isOk()) - .andExpect(content().string("Booking created")); - } - - // --- 예약 취소 테스트 --- - @Test - @DisplayName("예약 취소 - 사용자 토큰 정보 불일치 시 실패") - public void cancelBooking_should_return_403_whenUserTokenIsInvalid() throws Exception { - // Arrange - String bookingId = "BK123"; - String invalidUserToken = "INVALID_TOKEN"; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.delete("/api/v1/bookings/{id}", bookingId) - .header("Authorization", "Bearer " + invalidUserToken)) - .andExpect(status().isForbidden()) - .andExpect(content().string("Unauthorized user")); - } - - @Test - @DisplayName("예약 취소 - 성공") - public void cancelBooking_should_return_200_whenUserTokenIsValid() throws Exception { - // Arrange - String bookingId = "BK123"; - String validUserToken = "VALID_TOKEN"; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.delete("/api/v1/bookings/{id}", bookingId) - .header("Authorization", "Bearer " + validUserToken)) - .andExpect(status().isOk()) - .andExpect(content().string("Booking canceled")); - } - - @Test - @DisplayName("비회원 예약 목록 조회 - 사용자 정보와 알맞는 예약 정보가 없을 때") - public void getMyBookings_shouldReturn404_whenNoMatchingBooking() throws Exception { - // Arrange - String reservationId = "UNKNOWN"; - String name = "Nonexistent User"; - String birthDate = "2000-01-01"; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/bookings/my") - .param("bookingId", reservationId) - .param("name", name) - .param("birthDate", birthDate)) - .andExpect(status().isNotFound()); // 404 확인 - } - - @Test - @DisplayName("비회원 예약 목록 조회 - 사용자 정보와 일치하는 예약 정보 반환") - public void getMyBookings_shouldReturnBookingList_whenMatchingBookingExists() throws Exception { - // Arrange - String bookingId = "BK001"; - String name = "John Doe"; - String birthDate = "1990-01-01"; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/bookings/my") - .param("bookingId", bookingId) - .param("name", name) - .param("birthDate", birthDate)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].bookingId").value("BK001")) - .andExpect(jsonPath("$[0].reservationName").value("John Doe")); - } -} diff --git a/api/src/test/java/org/onewayticket/controller/FlightControllerIntegrationTest.java b/api/src/test/java/org/onewayticket/controller/FlightControllerIntegrationTest.java new file mode 100644 index 0000000..2727f94 --- /dev/null +++ b/api/src/test/java/org/onewayticket/controller/FlightControllerIntegrationTest.java @@ -0,0 +1,146 @@ +package org.onewayticket.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.onewayticket.dto.FlightDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class FlightControllerIntegrationTest { + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + private String baseUrl; + + @BeforeEach + void setup() { + baseUrl = "http://localhost:" + port; + } + + @Test + @DisplayName("저렴한 순으로 상위 9개 목적지에 대한 항공권 정보를 반환합니다.") + void Get_cheapest_flights() { + // Given + String url = baseUrl + "/api/v1/flights/cheapest"; + + // When + ResponseEntity response = restTemplate.getForEntity(url, FlightDto[].class); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().length > 0); + assertTrue(response.getBody()[0].price().compareTo(response.getBody()[1].price()) <= 0); + } + + + @Test + @DisplayName("입력한 출발지와 목적지에 해당하는 항공편이 리턴됩니다.") + void Search_flights_by_valid_info() { + // Given + String todayDate = LocalDate.now().toString(); // 현재 날짜를 "yyyy-MM-dd" 형식으로 가져옴 + String url = baseUrl + "/api/v1/flights/search?departure=ICN&destination=NRT&departureDate=" + todayDate + "&numberOfPassengers=1&sort=price"; + + // When + ResponseEntity response = restTemplate.getForEntity(url, FlightDto[].class); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals("ICN", response.getBody()[0].origin()); + assertEquals("NRT", response.getBody()[0].destination()); + assertEquals(todayDate, response.getBody()[0].departureTime().toLocalDate().toString()); + assertTrue(response.getBody()[0].price().compareTo(response.getBody()[1].price()) <= 0); + } + + @Test + @DisplayName("가격 정렬 필터를 넣으면 최저가 순으로 정렬되어 반환됩니다.") + void Search_flights_sorted_by_price() { + // Given + String url = baseUrl + "/api/v1/flights/search?departure=ICN&destination=NRT&departureDate=2023-12-01&numberOfPassengers=1&sort=price"; + + // When + ResponseEntity response = restTemplate.getForEntity(url, FlightDto[].class); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody()[0].price().compareTo(response.getBody()[1].price()) <= 0); + } + + @Test + @DisplayName("도착시간 필터를 넣으면 가장 빠른 도착시간 순으로 정렬되어 반환됩니다.") + void Search_flights_sorted_By_arrivaltime() { + // Given + String url = baseUrl + "/api/v1/flights/search?departure=ICN&destination=NRT&departureDate=2023-12-01&numberOfPassengers=1&sort=arrivalTime"; + + // When + ResponseEntity response = restTemplate.getForEntity(url, FlightDto[].class); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody()[0].arrivalTime().compareTo(response.getBody()[1].arrivalTime()) <= 0); + } + + @Test + @DisplayName("최소 비행시간 필터를 넣으면 가장 짧은 비행시간 순으로 정렬되어 반환됩니다.") + void search_flights_sorted_by_flight_duration() { + // Given + String url = baseUrl + "/api/v1/flights/search?departure=ICN&destination=NRT&departureDate=2023-12-01&numberOfPassengers=1&sort=flightDuration"; + + // When + ResponseEntity response = restTemplate.getForEntity(url, FlightDto[].class); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody()[0].flightDuration().compareTo(response.getBody()[1].flightDuration()) <= 0); + } + + + @Test + @DisplayName("유효한 flightId로 항공권 조회가 가능합니다.") + void Get_flightDetails_with_valid_flightId() { + // Given + String url = baseUrl + "/api/v1/flights/FL001"; + + // When + ResponseEntity response = restTemplate.getForEntity(url, FlightDto.class); + + // Then + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals("FL001", response.getBody().flightId()); + } + + @Test + @DisplayName("유효하지 않은 flightId는 400을 반환합니다.") + void testGetFlightDetailsBadRequest() { + // Given + String url = baseUrl + "/api/v1/flights/" + "A".repeat(51); + + // When + ResponseEntity response = restTemplate.getForEntity(url, Void.class); + + // Then + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + } + + +} diff --git a/api/src/test/java/org/onewayticket/controller/FlightControllerTest.java b/api/src/test/java/org/onewayticket/controller/FlightControllerTest.java deleted file mode 100644 index 7e9425d..0000000 --- a/api/src/test/java/org/onewayticket/controller/FlightControllerTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.onewayticket.controller; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import static org.hamcrest.collection.IsCollectionWithSize.hasSize; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -public class FlightControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Test - @DisplayName("최저가 항공편 조회 - 상위 9개 목적지 반환") - public void get_cheapest_flights_should_return_top_9_destinations_sorted_by_price() throws Exception { - // Arrange: 테스트 데이터는 컨트롤러에서 처리 - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/flights/cheapest")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(9))) // 상위 9개만 반환 확인 - .andExpect(jsonPath("$[0].destination").value("NRT")) // 가장 저렴한 항공편 - .andExpect(jsonPath("$[1].destination").value("HND")) // 두 번째 저렴한 항공편 - .andExpect(jsonPath("$[8].destination").value("SIN")); // 아홉 번째 항공편 - } - - - @Test - @DisplayName("항공권 검색 - 유효한 조건") - public void search_flights_should_return_200_for_valid_conditions() throws Exception { - // Arrange - String validSearchRequest = """ - { - "departure": "Seoul", - "destination": "Beijing", - "departureDate": "2023-12-25T10:00:00", - "numberOfPassengers": 1 - } - """; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/flights/search") - .contentType(MediaType.APPLICATION_JSON) - .content(validSearchRequest)) - .andExpect(status().isOk()); - } - - @Test - @DisplayName("항공권 검색 - 출발지와 목적지가 같은 경우") - public void search_flights_should_return_400_when_departure_and_destination_are_same() throws Exception { - // Arrange - String invalidSearchRequest = """ - { - "departure": "Seoul", - "destination": "Seoul", - "departureDate": "2023-12-25T10:00:00", - "numberOfPassengers": 1 - } - """; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/flights/search") - .contentType(MediaType.APPLICATION_JSON) - .content(invalidSearchRequest)) - .andExpect(status().isBadRequest()); - } - - @Test - @DisplayName("항공권 검색 - 조건에 맞는 데이터가 없는 경우") - public void search_flights_should_return_200_with_empty_list_when_no_matching_data() throws Exception { - // Arrange - String noMatchSearchRequest = """ - { - "departure": "Seoul", - "destination": "Mars", - "departureDate": "2023-12-25T10:00:00", - "numberOfPassengers": 1 - } - """; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/flights/search") - .contentType(MediaType.APPLICATION_JSON) - .content(noMatchSearchRequest)) - .andExpect(status().isOk()) - .andExpect(content().json("[]")); - } - - - @Test - @DisplayName("항공권 검색 - 탑승 인원 유효하지 않은 경우") - public void search_flights_should_return_400_when_passenger_count_is_invalid() throws Exception { - // Arrange - String invalidPassengerRequest = """ - { - "departure": "Seoul", - "destination": "Tokyo", - "departureDate": "2023-12-25T10:00:00", - "numberOfPassengers": -1 - } - """; - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/flights/search") - .contentType(MediaType.APPLICATION_JSON) - .content(invalidPassengerRequest)) - .andExpect(status().isBadRequest()); - } - - @Test - @DisplayName("항공편 상세 조회 - 경계값: 매우 긴 flightId") - public void get_flight_details_should_return_400_when_flight_id_is_too_long() throws Exception { - // Arrange - String longFlightId = "FL".repeat(200); // 매우 긴 ID 생성 - - // Act & Assert - mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/flights/{flightId}", longFlightId)) - .andExpect(status().isBadRequest()); // 상태 코드 400 확인 - } - -}