diff --git a/src/main/java/com/slam/concertreservation/application/facade/ConcertReservationApplication.java b/src/main/java/com/slam/concertreservation/application/facade/ConcertReservationApplication.java index 0d51dd0..38787a8 100644 --- a/src/main/java/com/slam/concertreservation/application/facade/ConcertReservationApplication.java +++ b/src/main/java/com/slam/concertreservation/application/facade/ConcertReservationApplication.java @@ -2,6 +2,7 @@ import com.slam.concertreservation.domain.concert.model.Concert; import com.slam.concertreservation.domain.concert.model.ConcertSchedule; +import com.slam.concertreservation.domain.concert.model.ConcertScheduleWithConcert; import com.slam.concertreservation.domain.concert.model.Seat; import com.slam.concertreservation.domain.concert.service.ConcertService; import com.slam.concertreservation.domain.point.model.PointHistory; @@ -116,8 +117,8 @@ public ConcertSchedule getConcertSchedule(Long concertScheduleId) { * * @return */ - public List getAvailableConcertSchedules() { - return concertService.getAvailableConcertSchedule(LocalDateTime.now()); + public List getAvailableConcertSchedulesWithConcert() { + return concertService.getAvailableConcertScheduleWithConcert(LocalDateTime.now()); } /** diff --git a/src/main/java/com/slam/concertreservation/domain/concert/model/ConcertScheduleWithConcert.java b/src/main/java/com/slam/concertreservation/domain/concert/model/ConcertScheduleWithConcert.java new file mode 100644 index 0000000..11fd928 --- /dev/null +++ b/src/main/java/com/slam/concertreservation/domain/concert/model/ConcertScheduleWithConcert.java @@ -0,0 +1,11 @@ +package com.slam.concertreservation.domain.concert.model; + +/** + * 예약 가능 공연 일정 조회 시 반환되는 공연 일정 & 공연 정보 조합 모델 + * @param concertSchedule + * @param concert + */ +public record ConcertScheduleWithConcert( + ConcertSchedule concertSchedule, + Concert concert +) {} diff --git a/src/main/java/com/slam/concertreservation/domain/concert/repository/ConcertRepository.java b/src/main/java/com/slam/concertreservation/domain/concert/repository/ConcertRepository.java index 4659001..1cb22c2 100644 --- a/src/main/java/com/slam/concertreservation/domain/concert/repository/ConcertRepository.java +++ b/src/main/java/com/slam/concertreservation/domain/concert/repository/ConcertRepository.java @@ -1,6 +1,7 @@ package com.slam.concertreservation.domain.concert.repository; import com.slam.concertreservation.domain.concert.model.Concert; +import java.util.List; import java.util.Optional; public interface ConcertRepository { @@ -8,4 +9,6 @@ public interface ConcertRepository { Concert save(Concert concert); Optional findById(Long id); + + List findAllById(List ids); } diff --git a/src/main/java/com/slam/concertreservation/domain/concert/service/ConcertService.java b/src/main/java/com/slam/concertreservation/domain/concert/service/ConcertService.java index c3eb5bb..00ee566 100644 --- a/src/main/java/com/slam/concertreservation/domain/concert/service/ConcertService.java +++ b/src/main/java/com/slam/concertreservation/domain/concert/service/ConcertService.java @@ -3,6 +3,7 @@ import com.slam.concertreservation.common.error.ErrorCode; import com.slam.concertreservation.domain.concert.model.Concert; import com.slam.concertreservation.domain.concert.model.ConcertSchedule; +import com.slam.concertreservation.domain.concert.model.ConcertScheduleWithConcert; import com.slam.concertreservation.domain.concert.model.Seat; import com.slam.concertreservation.domain.concert.repository.ConcertRepository; import com.slam.concertreservation.domain.concert.repository.ConcertScheduleRepository; @@ -11,6 +12,7 @@ import com.slam.concertreservation.infrastructure.persistence.redis.locking.RedissonDistributedLock; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; @@ -265,14 +267,31 @@ public List getAvailableSeatsOfConcertSchedule(Long concertScheduleId) { return availableSeats; } - /** - * 예약 가능 공연 일정 목록 조회. - * - * @param presentDateTime - * @return - */ - public List getAvailableConcertSchedule(LocalDateTime presentDateTime) { - return concertScheduleRepository.findAllAvailable(presentDateTime); + @Transactional(readOnly = true) + public List getAvailableConcertScheduleWithConcert(LocalDateTime presentDateTime) { + + // 예약 가능한 공연 일정 조회 + List schedules = concertScheduleRepository.findAllAvailable(presentDateTime); + + // 예약 가능한 공연 ID 목록 추출 + List concertIds = schedules.stream() + .map(ConcertSchedule::getConcertId) + .distinct() + .toList(); + + // 공연 ID 목록으로 공연 조회 + Map concerts = concertRepository.findAllById(concertIds) + .stream() + .collect( + java.util.stream.Collectors.toMap(Concert::getId, concert -> concert)); + + // ConcertSchedule과 Concert를 결합하여 ConcertScheduleWithConcert 생성 + return schedules.stream() + .filter(schedule -> concerts.get(schedule.getConcertId()) != null) + .map(schedule -> new ConcertScheduleWithConcert( + schedule, + concerts.get(schedule.getConcertId()))) + .toList(); } /** diff --git a/src/main/java/com/slam/concertreservation/infrastructure/persistence/jpa/impl/ConcertRepositoryImpl.java b/src/main/java/com/slam/concertreservation/infrastructure/persistence/jpa/impl/ConcertRepositoryImpl.java index 08032ab..8ede4a8 100644 --- a/src/main/java/com/slam/concertreservation/infrastructure/persistence/jpa/impl/ConcertRepositoryImpl.java +++ b/src/main/java/com/slam/concertreservation/infrastructure/persistence/jpa/impl/ConcertRepositoryImpl.java @@ -4,6 +4,7 @@ import com.slam.concertreservation.domain.concert.repository.ConcertRepository; import com.slam.concertreservation.infrastructure.persistence.jpa.ConcertJpaRepository; import com.slam.concertreservation.infrastructure.persistence.jpa.entities.ConcertJpaEntity; +import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -25,4 +26,12 @@ public Optional findById(Long concertId) { return concertJpaRepository.findById(concertId) .map(ConcertJpaEntity::toDomain); } + + @Override + public List findAllById(List ids) { + return concertJpaRepository.findAllById(ids) + .stream() + .map(ConcertJpaEntity::toDomain) + .toList(); + } } diff --git a/src/main/java/com/slam/concertreservation/interfaces/ConcertReservationController.java b/src/main/java/com/slam/concertreservation/interfaces/ConcertReservationController.java index bb5d43e..62a17ae 100644 --- a/src/main/java/com/slam/concertreservation/interfaces/ConcertReservationController.java +++ b/src/main/java/com/slam/concertreservation/interfaces/ConcertReservationController.java @@ -5,6 +5,7 @@ import com.slam.concertreservation.component.idempotency.Idempotent; import com.slam.concertreservation.domain.concert.model.Concert; import com.slam.concertreservation.domain.concert.model.ConcertSchedule; +import com.slam.concertreservation.domain.concert.model.ConcertScheduleWithConcert; import com.slam.concertreservation.domain.concert.model.Seat; import com.slam.concertreservation.domain.point.model.UserPointBalance; import com.slam.concertreservation.domain.queue.model.Token; @@ -118,14 +119,11 @@ public ResponseEntity registerConcertSchedule( */ @GetMapping("/concerts/schedules/available") public ResponseEntity> getAvailableConcertSchedules() { - List schedules = reservationApp.getAvailableConcertSchedules(); - List responses = schedules.stream() - .map(schedule -> { - Concert concert = reservationApp.getConcert(schedule.getConcertId()); - return ConcertScheduleResponse.from(schedule, concert); - }) - .toList(); - return ResponseEntity.ok(responses); + List schedules = reservationApp.getAvailableConcertSchedulesWithConcert(); + + return ResponseEntity.ok(schedules.stream() + .map(r -> ConcertScheduleResponse.from(r.concertSchedule(), r.concert())) + .toList()); } /** diff --git a/src/test/java/com/slam/concertreservation/application/NonConcurrentConcertReservationIntegrationTest.java b/src/test/java/com/slam/concertreservation/application/NonConcurrentConcertReservationIntegrationTest.java index 283c0d3..2981a4a 100644 --- a/src/test/java/com/slam/concertreservation/application/NonConcurrentConcertReservationIntegrationTest.java +++ b/src/test/java/com/slam/concertreservation/application/NonConcurrentConcertReservationIntegrationTest.java @@ -3,7 +3,9 @@ import com.slam.concertreservation.application.event.listener.PaymentEventListener; import com.slam.concertreservation.application.facade.ConcertReservationApplication; import com.slam.concertreservation.application.facade.UserApplication; +import com.slam.concertreservation.domain.concert.model.Concert; import com.slam.concertreservation.domain.concert.model.ConcertSchedule; +import com.slam.concertreservation.domain.concert.model.ConcertScheduleWithConcert; import com.slam.concertreservation.domain.concert.model.Seat; import com.slam.concertreservation.domain.concert.model.SeatStatus; import com.slam.concertreservation.domain.concert.repository.ConcertRepository; @@ -36,207 +38,223 @@ @SpringBootTest public class NonConcurrentConcertReservationIntegrationTest { - private static final Logger log = LoggerFactory.getLogger(NonConcurrentConcertReservationIntegrationTest.class); + private static final Logger log = LoggerFactory.getLogger(NonConcurrentConcertReservationIntegrationTest.class); - @Autowired - private ConcertReservationApplication concertReservationApplication; + @Autowired + private ConcertReservationApplication concertReservationApplication; - @Autowired - private PaymentEventListener paymentEventListener; + @Autowired + private PaymentEventListener paymentEventListener; - @Autowired - private UserApplication userApplication; + @Autowired + private UserApplication userApplication; - @Autowired - private ConcertRepository concertRepository; + @Autowired + private ConcertRepository concertRepository; - @Autowired - private ReservationRepository reservationRepository; + @Autowired + private ReservationRepository reservationRepository; - @Autowired - private ConcertScheduleRepository concertScheduleRepository; + @Autowired + private ConcertScheduleRepository concertScheduleRepository; - @Autowired - private UserRepository userRepository; + @Autowired + private UserRepository userRepository; - @Autowired - private TokenRepository tokenRepository; + @Autowired + private TokenRepository tokenRepository; - @Autowired - private SeatRepository seatRepository; + @Autowired + private SeatRepository seatRepository; - @Autowired - private PointHistoryRepository pointHistoryRepository; + @Autowired + private PointHistoryRepository pointHistoryRepository; - @Autowired - private UserPointBalanceRepository userPointBalanceRepository; + @Autowired + private UserPointBalanceRepository userPointBalanceRepository; - String userName = "userName"; - String concertName = "concertName"; - String artistName = "oasis"; - LocalDateTime concertDateTime = LocalDateTime.now().plusDays(15); // Concert in 15 days - LocalDateTime reservationStartAt = LocalDateTime.now(); // Reservation starts now - LocalDateTime reservationEndAt = LocalDateTime.now().plusDays(14); // Reservation ends 1 day before concert - ConcertSchedule concertSchedule = ConcertSchedule.create(1L, concertDateTime, reservationStartAt, - reservationEndAt); + String userName = "userName"; + String concertName = "concertName"; + String artistName = "oasis"; + LocalDateTime concertDateTime = LocalDateTime.now().plusDays(15); // Concert in 15 days + LocalDateTime reservationStartAt = LocalDateTime.now(); // Reservation starts now + LocalDateTime reservationEndAt = LocalDateTime.now().plusDays(14); // Reservation ends 1 day before concert + ConcertSchedule concertSchedule = ConcertSchedule.create(1L, concertDateTime, reservationStartAt, + reservationEndAt); - @Autowired - private PointService pointService; - @Autowired - private ReservationService reservationService; + @Autowired + private PointService pointService; + @Autowired + private ReservationService reservationService; - @Nested - class UserIntegrationTest { - @Test - @DisplayName("성공 : 회원 가입 시 회원 조회가 가능하다.") - void shouldFindUser_WhenUserSignedUp() { - // given - String userName = "한성경"; + @Nested + class UserIntegrationTest { + @Test + @DisplayName("성공 : 회원 가입 시 회원 조회가 가능하다.") + void shouldFindUser_WhenUserSignedUp() { + // given + String userName = "한성경"; - // when - User createdUser = userApplication.registerUser(userName); - User foundUser = userApplication.getUser(createdUser.getId()); + // when + User createdUser = userApplication.registerUser(userName); + User foundUser = userApplication.getUser(createdUser.getId()); - // then - Assertions.assertEquals(createdUser.getId(), foundUser.getId()); + // then + Assertions.assertEquals(createdUser.getId(), foundUser.getId()); + } } - } - - @Nested - class UserPointIntegrationTest { - - @Test - @DisplayName("성공 : 회원 가입 시 해당 회원의 포인트 잔액 0인 UserPointBalance가 생성되어 저장된다. 이때 INIT 타입의 PointHistory도 생성되어 저장된다.") - void shouldCreateAndSaveUserPointBalanceWith0AndInitPointHistory_WhenUserSignedUp() { - // given - String userName = "한성경"; - - // when - User createdUser = userApplication.registerUser(userName); - UserPointBalance actualUserPointBalance = concertReservationApplication - .getUserPointBalance(createdUser.getId()); - List actualPointHistory = concertReservationApplication - .getPointHistories(createdUser.getId()); - - // then - Assertions.assertEquals(0, actualUserPointBalance.balance().getAmount()); - Assertions.assertEquals(1, actualPointHistory.size()); - Assertions.assertEquals(0, actualPointHistory.get(0).transactionAmount()); - Assertions.assertEquals(PointTransactionType.INIT, actualPointHistory.get(0).transactionType()); - } - - @Test - @DisplayName("성공 : 회원의 포인트 잔액을 증액한다. 이때 PointHistory가 생성되어 저장된다.") - void shouldIncreaseUserPointBalanceAndCreatePointHistoryOfCharge_whenUserChargesPoint() { - // given - User user = userApplication.registerUser(userName); - - // when - UserPointBalance actualUserPointBalance = concertReservationApplication.chargeUserPoint(user.getId(), 1000); - List actualPointHistory = concertReservationApplication.getPointHistories(user.getId()); - - // then - Assertions.assertNotNull(actualUserPointBalance); - Assertions.assertEquals(1000, actualUserPointBalance.balance().getAmount()); - - Assertions.assertNotNull(actualPointHistory); - Assertions.assertEquals(2, actualPointHistory.size()); - } - - @Test - @DisplayName("성공 : 회원의 포인트 잔액을 감소한다. 이때 USE 타입의 PointHistory가 생성되어 저장된다.") - void shouldDecreaseUserPointBalanceAndCreatePointHistoryOfUse_whenUserUsesPoint() { - // given - User user = userApplication.registerUser("정종환"); - concertReservationApplication.chargeUserPoint(user.getId(), 1000); - - // when - UserPointBalance actualUserPointBalance = concertReservationApplication.useUserPoint(user.getId(), 500); - PointHistory expectedPointHistory = PointHistory.create(user.getId(), PointTransactionType.USE, 500); - List actualPointHistory = concertReservationApplication.getPointHistories(user.getId()); - - // then - Assertions.assertNotNull(actualUserPointBalance); - Assertions.assertEquals(500, actualUserPointBalance.balance().getAmount()); - - Assertions.assertNotNull(actualPointHistory); - Assertions.assertEquals(3, actualPointHistory.size()); - } - } - - @Nested - class ConcertReservationIntegrationTest { - - @Test - @DisplayName("성공 : 예약 가능한 공연 일정을 조회한다.") - void shouldSuccessfullyGetAvailableConcertSchedule() { - // given - // Create an available schedule (start time <= now) - ConcertSchedule expected = concertReservationApplication.registerConcertSchedule(1L, - concertDateTime.plusDays(1), LocalDateTime.now().minusMinutes(1), reservationEndAt.plusDays(1), - 1000); - - // when - List actual = concertReservationApplication.getAvailableConcertSchedules(); - - // then - Assertions.assertNotNull(actual); - // Verify that the schedule we just created is present in the results - boolean isPresent = actual.stream().anyMatch(cs -> cs.getId().equals(expected.getId())); - Assertions.assertTrue(isPresent, "Expected schedule should be available"); - } - - @Test - @DisplayName("성공 : 특정 공연 일정의 예약 가능 좌석 목록을 조회한다.") - void shouldSuccessfullyGetAvailableSeats() { - // given - ConcertSchedule concertSchedule = concertReservationApplication.registerConcertSchedule(1L, - concertDateTime, reservationStartAt, reservationEndAt, 1000); - - // when - List actual = concertReservationApplication.getAvailableSeats(concertSchedule.getId()); - // then - Assertions.assertNotNull(actual); - Assertions.assertEquals(Seat.MAX_SEAT_NUMBER, actual.size()); + @Nested + class UserPointIntegrationTest { + + @Test + @DisplayName("성공 : 회원 가입 시 해당 회원의 포인트 잔액 0인 UserPointBalance가 생성되어 저장된다. 이때 INIT 타입의 PointHistory도 생성되어 저장된다.") + void shouldCreateAndSaveUserPointBalanceWith0AndInitPointHistory_WhenUserSignedUp() { + // given + String userName = "한성경"; + + // when + User createdUser = userApplication.registerUser(userName); + UserPointBalance actualUserPointBalance = concertReservationApplication + .getUserPointBalance(createdUser.getId()); + List actualPointHistory = concertReservationApplication + .getPointHistories(createdUser.getId()); + + // then + Assertions.assertEquals(0, actualUserPointBalance.balance().getAmount()); + Assertions.assertEquals(1, actualPointHistory.size()); + Assertions.assertEquals(0, actualPointHistory.get(0).transactionAmount()); + Assertions.assertEquals(PointTransactionType.INIT, actualPointHistory.get(0).transactionType()); + } + + @Test + @DisplayName("성공 : 회원의 포인트 잔액을 증액한다. 이때 PointHistory가 생성되어 저장된다.") + void shouldIncreaseUserPointBalanceAndCreatePointHistoryOfCharge_whenUserChargesPoint() { + // given + User user = userApplication.registerUser(userName); + + // when + UserPointBalance actualUserPointBalance = concertReservationApplication + .chargeUserPoint(user.getId(), 1000); + List actualPointHistory = concertReservationApplication + .getPointHistories(user.getId()); + + // then + Assertions.assertNotNull(actualUserPointBalance); + Assertions.assertEquals(1000, actualUserPointBalance.balance().getAmount()); + + Assertions.assertNotNull(actualPointHistory); + Assertions.assertEquals(2, actualPointHistory.size()); + } + + @Test + @DisplayName("성공 : 회원의 포인트 잔액을 감소한다. 이때 USE 타입의 PointHistory가 생성되어 저장된다.") + void shouldDecreaseUserPointBalanceAndCreatePointHistoryOfUse_whenUserUsesPoint() { + // given + User user = userApplication.registerUser("정종환"); + concertReservationApplication.chargeUserPoint(user.getId(), 1000); + + // when + UserPointBalance actualUserPointBalance = concertReservationApplication + .useUserPoint(user.getId(), 500); + PointHistory expectedPointHistory = PointHistory.create(user.getId(), PointTransactionType.USE, + 500); + List actualPointHistory = concertReservationApplication + .getPointHistories(user.getId()); + + // then + Assertions.assertNotNull(actualUserPointBalance); + Assertions.assertEquals(500, actualUserPointBalance.balance().getAmount()); + + Assertions.assertNotNull(actualPointHistory); + Assertions.assertEquals(3, actualPointHistory.size()); + } } - @Test - @DisplayName("성공 : 특정 공연 일정의 특정 좌석을 예약하고 결제한다. 예약 후 해당 좌석 조회 시 상태는 UNAVAILABLE 이다. 또한 예약 상태는 PAID 이다. 잔액은 차감된다.") - void shouldSuccessfullyConfirmAndPaySeat() throws InterruptedException { - // given - User user = userApplication.registerUser(userName); - concertReservationApplication.chargeUserPoint(user.getId(), 2000); - - ConcertSchedule concertSchedule = concertReservationApplication.registerConcertSchedule(1L, - concertDateTime, reservationStartAt, reservationEndAt, 1000); - Seat seat = concertReservationApplication.getAvailableSeats(concertSchedule.getId()).get(0); - - // when - Seat assignedSeat = concertReservationApplication.assignSeat(concertSchedule.getId(), user.getId(), - seat.getId()); // 좌석 선점 - Reservation createdTemporaryReservation = concertReservationApplication.createTemporaryReservation( - user.getId(), concertSchedule.getId(), assignedSeat.getId(), assignedSeat.getPrice()); // 가예약 생성 - - concertReservationApplication.paymentRequestForReservation(user.getId(), seat.getPrice(), - createdTemporaryReservation.getId()); // 결제 요청 - - UserPointBalance updatedUserPointBalance = concertReservationApplication.getUserPointBalance(user.getId()); // 차감된 - // 사용자 - // 잔액 - Seat reservedSeat = concertReservationApplication.getSeat(seat.getId()); // 예약된 좌석 - Reservation confirmedReservation = concertReservationApplication - .getReservation(createdTemporaryReservation.getId()); // 확정된 예약 - log.warn("결제 완료 후 예약 확정되어 저장된 Reservation 상태 : {}", confirmedReservation.getStatus()); - log.warn("결제 완료 후 예약 확정되어 저장된 Reservation ID : {}", confirmedReservation.getId()); - - Thread.sleep(1000); - - // then - Assertions.assertEquals(updatedUserPointBalance.balance().getAmount(), 1000); - Assertions.assertEquals(SeatStatus.UNAVAILABLE, reservedSeat.getStatus()); - Assertions.assertEquals(ReservationStatus.CONFIRMED, confirmedReservation.getStatus()); - Assertions.assertEquals(1000, updatedUserPointBalance.balance().getAmount()); + @Nested + class ConcertReservationIntegrationTest { + + @Test + @DisplayName("성공 : 예약 가능한 공연 일정을 조회한다.") + void shouldSuccessfullyGetAvailableConcertSchedule() { + // given + // Create a concert first to ensure it exists for the schedule + Concert concert = concertReservationApplication.registerConcert("Test Concert", "Test Artist"); + + // Create an available schedule (start time <= now) + ConcertSchedule expected = concertReservationApplication.registerConcertSchedule( + concert.getId(), + concertDateTime.plusDays(1), LocalDateTime.now().minusMinutes(1), + reservationEndAt.plusDays(1), + 1000); + + // when + List actual = concertReservationApplication + .getAvailableConcertSchedulesWithConcert(); + + // then + Assertions.assertNotNull(actual); + // Verify that the schedule we just created is present in the results + boolean isPresent = actual.stream() + .anyMatch(cs -> cs.concertSchedule().getId().equals(expected.getId())); + Assertions.assertTrue(isPresent, "Expected schedule should be available"); + } + + @Test + @DisplayName("성공 : 특정 공연 일정의 예약 가능 좌석 목록을 조회한다.") + void shouldSuccessfullyGetAvailableSeats() { + // given + ConcertSchedule concertSchedule = concertReservationApplication.registerConcertSchedule(1L, + concertDateTime, reservationStartAt, reservationEndAt, 1000); + + // when + List actual = concertReservationApplication.getAvailableSeats(concertSchedule.getId()); + + // then + Assertions.assertNotNull(actual); + Assertions.assertEquals(Seat.MAX_SEAT_NUMBER, actual.size()); + } + + @Test + @DisplayName("성공 : 특정 공연 일정의 특정 좌석을 예약하고 결제한다. 예약 후 해당 좌석 조회 시 상태는 UNAVAILABLE 이다. 또한 예약 상태는 PAID 이다. 잔액은 차감된다.") + void shouldSuccessfullyConfirmAndPaySeat() throws InterruptedException { + // given + User user = userApplication.registerUser(userName); + concertReservationApplication.chargeUserPoint(user.getId(), 2000); + + ConcertSchedule concertSchedule = concertReservationApplication.registerConcertSchedule(1L, + concertDateTime, reservationStartAt, reservationEndAt, 1000); + Seat seat = concertReservationApplication.getAvailableSeats(concertSchedule.getId()).get(0); + + // when + Seat assignedSeat = concertReservationApplication.assignSeat(concertSchedule.getId(), + user.getId(), + seat.getId()); // 좌석 선점 + Reservation createdTemporaryReservation = concertReservationApplication + .createTemporaryReservation( + user.getId(), concertSchedule.getId(), assignedSeat.getId(), + assignedSeat.getPrice()); // 가예약 생성 + + concertReservationApplication.paymentRequestForReservation(user.getId(), seat.getPrice(), + createdTemporaryReservation.getId()); // 결제 요청 + + UserPointBalance updatedUserPointBalance = concertReservationApplication + .getUserPointBalance(user.getId()); // 차감된 + // 사용자 + // 잔액 + Seat reservedSeat = concertReservationApplication.getSeat(seat.getId()); // 예약된 좌석 + Reservation confirmedReservation = concertReservationApplication + .getReservation(createdTemporaryReservation.getId()); // 확정된 예약 + log.warn("결제 완료 후 예약 확정되어 저장된 Reservation 상태 : {}", confirmedReservation.getStatus()); + log.warn("결제 완료 후 예약 확정되어 저장된 Reservation ID : {}", confirmedReservation.getId()); + + Thread.sleep(1000); + + // then + Assertions.assertEquals(updatedUserPointBalance.balance().getAmount(), 1000); + Assertions.assertEquals(SeatStatus.UNAVAILABLE, reservedSeat.getStatus()); + Assertions.assertEquals(ReservationStatus.CONFIRMED, confirmedReservation.getStatus()); + Assertions.assertEquals(1000, updatedUserPointBalance.balance().getAmount()); + } } - } } diff --git a/src/test/java/com/slam/concertreservation/domain/concert/service/ConcertServiceUnitTest.java b/src/test/java/com/slam/concertreservation/domain/concert/service/ConcertServiceUnitTest.java index 05e28e8..c5bc6be 100644 --- a/src/test/java/com/slam/concertreservation/domain/concert/service/ConcertServiceUnitTest.java +++ b/src/test/java/com/slam/concertreservation/domain/concert/service/ConcertServiceUnitTest.java @@ -3,6 +3,7 @@ import com.slam.concertreservation.common.error.ErrorCode; import com.slam.concertreservation.domain.concert.model.Concert; import com.slam.concertreservation.domain.concert.model.ConcertSchedule; +import com.slam.concertreservation.domain.concert.model.ConcertScheduleWithConcert; import com.slam.concertreservation.domain.concert.model.Seat; import com.slam.concertreservation.domain.concert.model.SeatStatus; import com.slam.concertreservation.domain.concert.repository.ConcertRepository; @@ -17,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import java.util.List; import java.util.Optional; import org.mockito.MockitoAnnotations; import org.springframework.context.ApplicationEventPublisher; @@ -321,4 +323,59 @@ void shouldThrowIllegalStateException_WhenSeatAlreadyUnassigned() { .isInstanceOf(BusinessRuleViolationException.class); } } + + @Nested + class GetAvailableConcertScheduleWithConcertUnitTest { + @Test + @DisplayName("성공 : 예약 가능한 공연 일정과 공연 정보를 함께 조회한다.") + void shouldReturnConcertSchedulesWithConcert_WhenAvailableSchedulesExist() { + // given + LocalDateTime now = LocalDateTime.now(); + Concert concert = Concert.create("OASIS REUNION", "oasis"); + ConcertSchedule schedule = ConcertSchedule.create(concert.getId(), now.plusDays(1), now, + now.plusDays(1)); + + when(concertScheduleRepository.findAllAvailable(any(LocalDateTime.class))) + .thenReturn(List.of(schedule)); + when(concertRepository.findAllById(anyList())) + .thenReturn(List.of(concert)); + + // when + List actual = concertService + .getAvailableConcertScheduleWithConcert(now); + + // then + assertEquals(1, actual.size()); + assertEquals(schedule, actual.get(0).concertSchedule()); + assertEquals(concert, actual.get(0).concert()); + } + + @Test + @DisplayName("성공 : 공연 정보가 없는 공연 일정은 조회 결과에서 제외된다.") + void shouldFilterOutSchedules_WhenConcertNotFound() { + // given + LocalDateTime now = LocalDateTime.now(); + Concert concert = Concert.create("OASIS REUNION", "oasis"); + ConcertSchedule schedule1 = ConcertSchedule.create(concert.getId(), now.plusDays(1), now, + now.plusDays(1)); + ConcertSchedule schedule2 = ConcertSchedule.create(2L, now.plusDays(1), now, now.plusDays(1)); // Concert + // not + // found + + when(concertScheduleRepository.findAllAvailable(any(LocalDateTime.class))) + .thenReturn(List.of(schedule1, schedule2)); + when(concertRepository.findAllById(anyList())) + .thenReturn(List.of(concert)); + + // when + List actual = concertService + .getAvailableConcertScheduleWithConcert(now); + + // then + assertEquals(1, actual.size()); + assertEquals(schedule1, actual.get(0).concertSchedule()); + assertEquals(concert, actual.get(0).concert()); + } + } + } \ No newline at end of file