test: ReservationService 테스트 코틀린 전환

This commit is contained in:
이상진 2025-07-20 16:37:43 +09:00
parent c2d4e10160
commit 2b234511ac
3 changed files with 205 additions and 239 deletions

View File

@ -1,239 +0,0 @@
package roomescape.reservation.business;
import static org.assertj.core.api.Assertions.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import roomescape.common.exception.RoomescapeException;
import roomescape.member.business.MemberService;
import roomescape.member.infrastructure.persistence.MemberEntity;
import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.infrastructure.persistence.Role;
import roomescape.reservation.infrastructure.persistence.ReservationEntity;
import roomescape.reservation.infrastructure.persistence.ReservationRepository;
import roomescape.reservation.infrastructure.persistence.ReservationStatus;
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity;
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository;
import roomescape.reservation.web.ReservationRequest;
import roomescape.reservation.web.ReservationResponse;
import roomescape.reservation.web.WaitingRequest;
import roomescape.theme.business.ThemeService;
import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.infrastructure.persistence.ThemeRepository;
@SpringBootTest
@Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
@Import({ReservationService.class, MemberService.class, ReservationTimeService.class, ThemeService.class})
class ReservationServiceTest {
@Autowired
ReservationTimeRepository reservationTimeRepository;
@Autowired
ReservationRepository reservationRepository;
@Autowired
ThemeRepository themeRepository;
@Autowired
MemberRepository memberRepository;
@Autowired
private ReservationService reservationService;
@Test
@DisplayName("예약을 추가할때 이미 예약이 존재하면 예외가 발생한다.")
void reservationAlreadyExistFail() {
// given
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, LocalTime.of(12, 30)));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
MemberEntity member1 = memberRepository.save(
new MemberEntity(null, "name", "email@email.com", "password", Role.MEMBER));
MemberEntity member2 = memberRepository.save(
new MemberEntity(null, "name2", "email2@email.com", "password", Role.MEMBER));
LocalDate date = LocalDate.now().plusDays(1L);
// when
reservationService.addReservation(
new ReservationRequest(date, reservationTime.getId(), theme.getId(), "paymentKey", "orderId",
1000L, "paymentType"), member2.getId());
// then
assertThatThrownBy(() -> reservationService.addReservation(
new ReservationRequest(date, reservationTime.getId(), theme.getId(), "paymentKey", "orderId",
1000L, "paymentType"), member1.getId()))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("이미 예약한 멤버가 같은 테마에 대기를 신청하면 예외가 발생한다.")
void requestWaitWhenAlreadyReserveFail() {
// given
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, LocalTime.of(12, 30)));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "name", "email@email.com", "password", Role.MEMBER));
LocalDate date = LocalDate.now().plusDays(1L);
// when
reservationService.addReservation(
new ReservationRequest(date, reservationTime.getId(), theme.getId(), "paymentKey", "orderId",
1000L, "paymentType"), member.getId());
// then
assertThatThrownBy(() -> reservationService.addWaiting(
new WaitingRequest(date, reservationTime.getId(), theme.getId()), member.getId()))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("예약 대기를 두 번 이상 요청하면 예외가 발생한다.")
void requestWaitTwiceFail() {
// given
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, LocalTime.of(12, 30)));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "name", "email@email.com", "password", Role.MEMBER));
MemberEntity member1 = memberRepository.save(
new MemberEntity(null, "name1", "email1@email.com", "password", Role.MEMBER));
LocalDate date = LocalDate.now().plusDays(1L);
// when
reservationService.addReservation(
new ReservationRequest(date, reservationTime.getId(), theme.getId(), "paymentKey", "orderId",
1000L, "paymentType"), member.getId());
reservationService.addWaiting(
new WaitingRequest(date, reservationTime.getId(), theme.getId()), member1.getId());
// then
assertThatThrownBy(() -> reservationService.addWaiting(
new WaitingRequest(date, reservationTime.getId(), theme.getId()), member1.getId()))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("이미 지난 날짜로 예약을 생성하면 예외가 발생한다.")
void beforeDateReservationFail() {
// given
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, LocalTime.of(12, 30)));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "name", "email@email.com", "password", Role.MEMBER));
LocalDate beforeDate = LocalDate.now().minusDays(1L);
// when & then
assertThatThrownBy(() -> reservationService.addReservation(
new ReservationRequest(beforeDate, reservationTime.getId(), theme.getId(), "paymentKey", "orderId",
1000L, "paymentType"), member.getId()))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("현재 날짜가 예약 당일이지만, 이미 지난 시간으로 예약을 생성하면 예외가 발생한다.")
void beforeTimeReservationFail() {
// given
LocalDateTime beforeTime = LocalDateTime.now().minusHours(1L).withNano(0);
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, beforeTime.toLocalTime()));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "name", "email@email.com", "password", Role.MEMBER));
// when & then
assertThatThrownBy(() -> reservationService.addReservation(
new ReservationRequest(beforeTime.toLocalDate(), reservationTime.getId(), theme.getId(), "paymentKey",
"orderId", 1000L, "paymentType"), member.getId()))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("존재하지 않는 회원이 예약을 생성하려고 하면 예외가 발생한다.")
void notExistMemberReservationFail() {
// given
LocalDateTime beforeTime = LocalDateTime.now().minusDays(1L).withNano(0);
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, beforeTime.toLocalTime()));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Long NotExistMemberId = 1L;
// when & then
assertThatThrownBy(() -> reservationService.addReservation(
new ReservationRequest(beforeTime.toLocalDate(), reservationTime.getId(), theme.getId(), "paymentKey",
"orderId", 1000L, "paymentType"),
NotExistMemberId))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("예약을 조회할 때 종료 날짜가 시작 날짜 이전이면 예외가 발생한다.")
void invalidDateRange() {
// given
LocalDate dateFrom = LocalDate.now().plusDays(1);
LocalDate dateTo = LocalDate.now();
// when & then
assertThatThrownBy(() -> reservationService.findFilteredReservations(null, null, dateFrom, dateTo))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("대기중인 예약을 승인할 때, 기존에 예약이 존재하면 예외가 발생한다.")
void confirmWaitingWhenReservationExist() {
// given
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, LocalTime.of(12, 30)));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
MemberEntity admin = memberRepository.save(
new MemberEntity(null, "admin", "admin@email.com", "password", Role.ADMIN));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "name", "email@email.com", "password", Role.MEMBER));
MemberEntity member1 = memberRepository.save(
new MemberEntity(null, "name1", "email1@email.com", "password", Role.MEMBER));
reservationService.addReservation(
new ReservationRequest(LocalDate.now().plusDays(1L), reservationTime.getId(), theme.getId(),
"paymentKey", "orderId",
1000L, "paymentType"), member.getId());
ReservationResponse waiting = reservationService.addWaiting(
new WaitingRequest(LocalDate.now().plusDays(1L), reservationTime.getId(), theme.getId()),
member1.getId());
// when & then
assertThatThrownBy(() -> reservationService.approveWaiting(waiting.id, admin.getId()))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("대기중인 예약을 확정한다.")
void approveWaiting() {
// given
ReservationTimeEntity reservationTime = reservationTimeRepository.save(
new ReservationTimeEntity(null, LocalTime.of(12, 30)));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
MemberEntity admin = memberRepository.save(
new MemberEntity(null, "admin", "admin@email.com", "password", Role.ADMIN));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "name", "email@email.com", "password", Role.MEMBER));
// when
ReservationResponse waiting = reservationService.addWaiting(
new WaitingRequest(LocalDate.now().plusDays(1L), reservationTime.getId(), theme.getId()),
member.getId());
reservationService.approveWaiting(waiting.id, admin.getId());
// then
ReservationEntity confirmed = reservationRepository.findById(waiting.id).get();
assertThat(confirmed.getReservationStatus()).isEqualTo(ReservationStatus.CONFIRMED_PAYMENT_REQUIRED);
}
}

View File

@ -0,0 +1,175 @@
package roomescape.reservation.business
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.member.business.MemberService
import roomescape.member.infrastructure.persistence.Role
import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.theme.business.ThemeService
import roomescape.util.MemberFixture
import roomescape.util.ReservationFixture
import roomescape.util.ReservationTimeFixture
import java.time.LocalDate
import java.time.LocalTime
class ReservationServiteTest : FunSpec({
val reservationRepository: ReservationRepository = mockk()
val reservationTimeService: ReservationTimeService = mockk()
val memberService: MemberService = mockk()
val themeService: ThemeService = mockk()
val reservationService = ReservationService(
reservationRepository,
reservationTimeService,
memberService,
themeService
)
context("예약을 추가할 때") {
test("이미 예약이 있으면 예외를 던진다.") {
every {
reservationRepository.exists(any())
} returns true
val reservationRequest = ReservationFixture.createRequest()
shouldThrow<RoomescapeException> {
reservationService.addReservation(reservationRequest, 1L)
}.also {
it.errorType shouldBe ErrorType.RESERVATION_DUPLICATED
}
}
context("날짜, 시간이 잘못 입력되면 예외를 던진다.") {
every {
reservationRepository.exists(any())
} returns false
every {
themeService.findThemeById(any())
} returns mockk()
every {
memberService.findById(any())
} returns mockk()
test("지난 날짜이면 예외를 던진다.") {
val reservationRequest = ReservationFixture.createRequest().copy(
date = LocalDate.now().minusDays(1)
)
every {
reservationTimeService.findTimeById(any())
} returns ReservationTimeFixture.create()
shouldThrow<RoomescapeException> {
reservationService.addReservation(reservationRequest, 1L)
}.also {
it.errorType shouldBe ErrorType.RESERVATION_PERIOD_IN_PAST
}
}
test("지난 시간이면 예외를 던진다.") {
val reservationRequest = ReservationFixture.createRequest().copy(
date = LocalDate.now(),
)
every {
reservationTimeService.findTimeById(reservationRequest.timeId)
} returns ReservationTimeFixture.create(
startAt = LocalTime.now().minusMinutes(1)
)
shouldThrow<RoomescapeException> {
reservationService.addReservation(reservationRequest, 1L)
}.also {
it.errorType shouldBe ErrorType.RESERVATION_PERIOD_IN_PAST
}
}
}
}
context("예약 대기를 걸 때") {
test("이미 예약한 회원이 같은 날짜와 테마로 대기를 걸면 예외를 던진다.") {
val reservationRequest = ReservationFixture.createRequest().copy(
date = LocalDate.now(),
themeId = 1L,
timeId = 1L,
)
every {
reservationRepository.exists(any())
} returns true
shouldThrow<RoomescapeException> {
val waitingRequest = ReservationFixture.createWaitingRequest(
date = reservationRequest.date,
themeId = reservationRequest.themeId,
timeId = reservationRequest.timeId
)
reservationService.addWaiting(waitingRequest, 1L)
}.also {
it.errorType shouldBe ErrorType.HAS_RESERVATION_OR_WAITING
}
}
}
context("예약을 조회할 때") {
test("종료 날짜가 시작 날짜보다 이전이면 예외를 던진다.") {
val startFrom = LocalDate.now()
val endAt = startFrom.minusDays(1)
shouldThrow<RoomescapeException> {
reservationService.findFilteredReservations(
null,
null,
startFrom,
endAt
)
}.also {
it.errorType shouldBe ErrorType.INVALID_DATE_RANGE
}
}
}
context("대기중인 예약을 승인할 때") {
test("관리자가 아니면 예외를 던진다.") {
val member = MemberFixture.create(id = 1L, role = Role.MEMBER)
every {
memberService.findById(any())
} returns member
shouldThrow<RoomescapeException> {
reservationService.approveWaiting(1L, member.id!!)
}.also {
it.errorType shouldBe ErrorType.PERMISSION_DOES_NOT_EXIST
}
}
test("이미 확정된 예약이 있으면 예외를 던진다.") {
val member = MemberFixture.create(id = 1L, role = Role.ADMIN)
val reservationId = 1L
every {
memberService.findById(any())
} returns member
every {
reservationRepository.isExistConfirmedReservation(reservationId)
} returns true
shouldThrow<RoomescapeException> {
reservationService.approveWaiting(reservationId, member.id!!)
}.also {
it.errorType shouldBe ErrorType.RESERVATION_DUPLICATED
}
}
}
})

View File

@ -11,6 +11,8 @@ import roomescape.payment.web.PaymentCancel
import roomescape.reservation.infrastructure.persistence.ReservationEntity import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationStatus import roomescape.reservation.infrastructure.persistence.ReservationStatus
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
import roomescape.reservation.web.ReservationRequest
import roomescape.reservation.web.WaitingRequest
import roomescape.theme.infrastructure.persistence.ThemeEntity import roomescape.theme.infrastructure.persistence.ThemeEntity
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
@ -76,6 +78,34 @@ object ReservationFixture {
member: MemberEntity = MemberFixture.create(), member: MemberEntity = MemberFixture.create(),
status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
): ReservationEntity = ReservationEntity(id, date, reservationTime, theme, member, status) ): ReservationEntity = ReservationEntity(id, date, reservationTime, theme, member, status)
fun createRequest(
date: LocalDate = LocalDate.now().plusWeeks(1),
themeId: Long = 1L,
timeId: Long = 1L,
paymentKey: String = "paymentKey",
orderId: String = "orderId",
amount: Long = 10000L,
paymentType: String = "NORMAL",
): ReservationRequest = ReservationRequest(
date = date,
timeId = timeId,
themeId = themeId,
paymentKey = paymentKey,
orderId = orderId,
amount = amount,
paymentType = paymentType
)
fun createWaitingRequest(
date: LocalDate = LocalDate.now().plusWeeks(1),
themeId: Long = 1L,
timeId: Long = 1L
): WaitingRequest = WaitingRequest(
date = date,
timeId = timeId,
themeId = themeId
)
} }
object JwtFixture { object JwtFixture {