generated from pricelees/issue-pr-template
test: 예약 API 테스트 추가
This commit is contained in:
parent
b847e59d6f
commit
211edcaffd
571
src/test/kotlin/roomescape/reservation/ReservationApiTest.kt
Normal file
571
src/test/kotlin/roomescape/reservation/ReservationApiTest.kt
Normal file
@ -0,0 +1,571 @@
|
||||
package roomescape.reservation
|
||||
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.http.HttpStatus
|
||||
import roomescape.common.exception.CommonErrorCode
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.payment.exception.PaymentErrorCode
|
||||
import roomescape.payment.infrastructure.common.BankCode
|
||||
import roomescape.payment.infrastructure.common.CardIssuerCode
|
||||
import roomescape.payment.infrastructure.common.EasyPayCompanyCode
|
||||
import roomescape.payment.infrastructure.persistence.PaymentDetailRepository
|
||||
import roomescape.reservation.exception.ReservationErrorCode
|
||||
import roomescape.reservation.infrastructure.persistence.CanceledReservationRepository
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||
import roomescape.reservation.web.ReservationCancelRequest
|
||||
import roomescape.schedule.infrastructure.persistence.ScheduleEntity
|
||||
import roomescape.schedule.infrastructure.persistence.ScheduleRepository
|
||||
import roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.util.*
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
class ReservationApiTest(
|
||||
private val reservationRepository: ReservationRepository,
|
||||
private val canceledReservationRepository: CanceledReservationRepository,
|
||||
private val scheduleRepository: ScheduleRepository,
|
||||
private val paymentDetailRepository: PaymentDetailRepository,
|
||||
) : FunSpecSpringbootTest() {
|
||||
|
||||
init {
|
||||
context("결제 전 임시 예약을 생성한다.") {
|
||||
val commonRequest = ReservationFixture.pendingCreateRequest
|
||||
|
||||
test("정상 생성") {
|
||||
val schedule: ScheduleEntity = dummyInitializer.createSchedule(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
request = ScheduleFixture.createRequest,
|
||||
status = ScheduleStatus.HOLD
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
using = {
|
||||
body(commonRequest.copy(scheduleId = schedule.id))
|
||||
},
|
||||
on = {
|
||||
post("/reservations/pending")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
}
|
||||
).also {
|
||||
val reservation: ReservationEntity =
|
||||
reservationRepository.findByIdOrNull(it.extract().path("data.id"))
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
|
||||
reservation.status shouldBe ReservationStatus.PENDING
|
||||
reservation.scheduleId shouldBe schedule.id
|
||||
reservation.reserverName shouldBe commonRequest.reserverName
|
||||
}
|
||||
}
|
||||
|
||||
test("예약을 생성할 때 해당 일정이 ${ScheduleStatus.HOLD} 상태가 아니면 실패한다.") {
|
||||
val schedule: ScheduleEntity = dummyInitializer.createSchedule(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
request = ScheduleFixture.createRequest,
|
||||
status = ScheduleStatus.AVAILABLE
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
using = {
|
||||
body(commonRequest.copy(scheduleId = schedule.id))
|
||||
},
|
||||
on = {
|
||||
post("/reservations/pending")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("code", equalTo(ReservationErrorCode.SCHEDULE_NOT_HOLD.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
test("예약 인원이 테마의 최소 인원보다 작거나 최대 인원보다 많으면 실패한다.") {
|
||||
val adminToken = loginUtil.loginAsAdmin()
|
||||
val theme: ThemeEntity = dummyInitializer.createTheme(
|
||||
adminToken = adminToken,
|
||||
request = ThemeFixture.createRequest
|
||||
)
|
||||
|
||||
val schedule: ScheduleEntity = dummyInitializer.createSchedule(
|
||||
adminToken = adminToken,
|
||||
request = ScheduleFixture.createRequest.copy(themeId = theme.id),
|
||||
status = ScheduleStatus.HOLD
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
using = {
|
||||
body(
|
||||
commonRequest.copy(
|
||||
scheduleId = schedule.id,
|
||||
participantCount = ((theme.minParticipants - 1).toShort())
|
||||
)
|
||||
)
|
||||
},
|
||||
on = {
|
||||
post("/reservations/pending")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("code", equalTo(ReservationErrorCode.INVALID_PARTICIPANT_COUNT.errorCode))
|
||||
}
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
using = {
|
||||
body(
|
||||
commonRequest.copy(
|
||||
scheduleId = schedule.id,
|
||||
participantCount = ((theme.maxParticipants + 1).toShort())
|
||||
)
|
||||
)
|
||||
},
|
||||
on = {
|
||||
post("/reservations/pending")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("code", equalTo(ReservationErrorCode.INVALID_PARTICIPANT_COUNT.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
context("필수 입력값이 입력되지 않으면 실패한다.") {
|
||||
test("예약자명") {
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
using = {
|
||||
body(commonRequest.copy(reserverName = ""))
|
||||
},
|
||||
on = {
|
||||
post("/reservations/pending")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("code", equalTo(CommonErrorCode.INVALID_INPUT_VALUE.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
test("예약자 연락처") {
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
using = {
|
||||
body(commonRequest.copy(reserverContact = ""))
|
||||
},
|
||||
on = {
|
||||
post("/reservations/pending")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("code", equalTo(CommonErrorCode.INVALID_INPUT_VALUE.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("예약을 확정한다.") {
|
||||
test("정상 응답") {
|
||||
val userToken = loginUtil.loginAsUser()
|
||||
|
||||
val reservation: ReservationEntity = dummyInitializer.createPendingReservation(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
reserverToken = userToken,
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = userToken,
|
||||
on = {
|
||||
post("/reservations/${reservation.id}/confirm")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
}
|
||||
).also {
|
||||
val updatedReservation = reservationRepository.findByIdOrNull(reservation.id)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
|
||||
val updatedSchedule = scheduleRepository.findByIdOrNull(updatedReservation.scheduleId)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
|
||||
updatedSchedule.status shouldBe ScheduleStatus.RESERVED
|
||||
updatedReservation.status shouldBe ReservationStatus.CONFIRMED
|
||||
}
|
||||
}
|
||||
|
||||
test("예약이 없으면 실패한다.") {
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
on = {
|
||||
post("/reservations/$INVALID_PK/confirm")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.NOT_FOUND.value())
|
||||
body("code", equalTo(ReservationErrorCode.RESERVATION_NOT_FOUND.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("예약을 취소한다.") {
|
||||
test("정상 응답") {
|
||||
val userToken = loginUtil.loginAsUser()
|
||||
|
||||
val reservation: ReservationEntity = dummyInitializer.createConfirmReservation(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
reserverToken = userToken,
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = userToken,
|
||||
using = {
|
||||
body(ReservationCancelRequest(cancelReason = "test"))
|
||||
},
|
||||
on = {
|
||||
post("/reservations/${reservation.id}/cancel")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
}
|
||||
).also {
|
||||
val updatedReservation = reservationRepository.findByIdOrNull(reservation.id)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
val updatedSchedule = scheduleRepository.findByIdOrNull(updatedReservation.scheduleId)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
|
||||
updatedReservation.status shouldBe ReservationStatus.CANCELED
|
||||
updatedSchedule.status shouldBe ScheduleStatus.AVAILABLE
|
||||
canceledReservationRepository.findAll()[0].reservationId shouldBe updatedReservation.id
|
||||
}
|
||||
}
|
||||
|
||||
test("예약이 없으면 실패한다.") {
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
using = {
|
||||
body(ReservationCancelRequest(cancelReason = "test"))
|
||||
},
|
||||
on = {
|
||||
post("/reservations/$INVALID_PK/cancel")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.NOT_FOUND.value())
|
||||
body("code", equalTo(ReservationErrorCode.RESERVATION_NOT_FOUND.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
test("관리자가 아닌 회원은 다른 회원의 예약을 취소할 수 없다.") {
|
||||
val reservation: ReservationEntity = dummyInitializer.createConfirmReservation(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
reserverToken = loginUtil.loginAsUser(),
|
||||
)
|
||||
|
||||
val otherUserToken = loginUtil.login("other@example.com", "other", role = Role.MEMBER)
|
||||
|
||||
runTest(
|
||||
token = otherUserToken,
|
||||
using = {
|
||||
body(ReservationCancelRequest(cancelReason = "test"))
|
||||
},
|
||||
on = {
|
||||
post("/reservations/${reservation.id}/cancel")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.FORBIDDEN.value())
|
||||
body("code", equalTo(ReservationErrorCode.NO_PERMISSION_TO_CANCEL_RESERVATION.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
test("관리자는 다른 회원의 예약을 취소할 수 있다.") {
|
||||
val reservation: ReservationEntity = dummyInitializer.createConfirmReservation(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
reserverToken = loginUtil.loginAsAdmin(),
|
||||
)
|
||||
|
||||
val otherAdminToken = loginUtil.login("admin1@example.com", "admin1", role = Role.ADMIN)
|
||||
|
||||
runTest(
|
||||
token = otherAdminToken,
|
||||
using = {
|
||||
body(ReservationCancelRequest(cancelReason = "test"))
|
||||
},
|
||||
on = {
|
||||
post("/reservations/${reservation.id}/cancel")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
}
|
||||
).also {
|
||||
val updatedReservation = reservationRepository.findByIdOrNull(reservation.id)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
val updatedSchedule = scheduleRepository.findByIdOrNull(updatedReservation.scheduleId)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
|
||||
updatedSchedule.status shouldBe ScheduleStatus.AVAILABLE
|
||||
updatedReservation.status shouldBe ReservationStatus.CANCELED
|
||||
canceledReservationRepository.findAll()[0].reservationId shouldBe updatedReservation.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("나의 예약 목록을 조회한다.") {
|
||||
test("정상 응답") {
|
||||
val userToken = loginUtil.loginAsUser()
|
||||
val adminToken = loginUtil.loginAsAdmin()
|
||||
|
||||
for (i in 1..3) {
|
||||
dummyInitializer.createConfirmReservation(
|
||||
adminToken = adminToken,
|
||||
reserverToken = userToken,
|
||||
themeRequest = ThemeFixture.createRequest.copy(name = "theme-$i"),
|
||||
scheduleRequest = ScheduleFixture.createRequest.copy(
|
||||
date = LocalDate.now().plusDays(i.toLong()),
|
||||
time = LocalTime.now().plusHours(i.toLong())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
runTest(
|
||||
token = userToken,
|
||||
on = {
|
||||
get("/reservations/summary")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
body("data.reservations.size()", equalTo(3))
|
||||
assertProperties(
|
||||
props = setOf("id", "themeName", "date", "startAt", "status"),
|
||||
propsNameIfList = "reservations"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("예약 상세 정보를 조회한다.") {
|
||||
context("정상 응답") {
|
||||
val commonPaymentRequest = PaymentFixture.confirmRequest
|
||||
|
||||
lateinit var reservation: ReservationEntity
|
||||
|
||||
beforeTest {
|
||||
reservation = dummyInitializer.createConfirmReservation(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
reserverToken = loginUtil.loginAsUser(),
|
||||
)
|
||||
}
|
||||
|
||||
test("카드 결제") {
|
||||
dummyInitializer.createPayment(
|
||||
reservationId = reservation.id,
|
||||
request = commonPaymentRequest,
|
||||
cardDetail = PaymentFixture.cardDetail(
|
||||
amount = commonPaymentRequest.amount,
|
||||
issuerCode = CardIssuerCode.SHINHAN
|
||||
)
|
||||
)
|
||||
|
||||
runDetailRetrieveTest(reservation).also {
|
||||
it["method"] shouldBe "카드"
|
||||
|
||||
with(it.get("detail") as LinkedHashMap<*, *>) {
|
||||
this["type"] shouldBe "CARD"
|
||||
this["issuerCode"] shouldBe "신한"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("카드 + 간편결제") {
|
||||
dummyInitializer.createPayment(
|
||||
reservationId = reservation.id,
|
||||
request = commonPaymentRequest,
|
||||
easyPayDetail = PaymentFixture.easypayDetail(
|
||||
amount = 0,
|
||||
provider = EasyPayCompanyCode.TOSSPAY,
|
||||
),
|
||||
cardDetail = PaymentFixture.cardDetail(
|
||||
amount = commonPaymentRequest.amount,
|
||||
)
|
||||
)
|
||||
|
||||
runDetailRetrieveTest(reservation).also {
|
||||
it["method"] shouldBe "간편결제"
|
||||
|
||||
with(it.get("detail") as LinkedHashMap<*, *>) {
|
||||
this["type"] shouldBe "CARD"
|
||||
this["easypayProviderName"] shouldBe "토스페이"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("결제 취소 / 카드 + 간편결제 + 포인트 사용") {
|
||||
val point = 5_000
|
||||
|
||||
dummyInitializer.createPayment(
|
||||
reservationId = reservation.id,
|
||||
request = commonPaymentRequest,
|
||||
easyPayDetail = PaymentFixture.easypayDetail(
|
||||
amount = 0,
|
||||
provider = EasyPayCompanyCode.TOSSPAY,
|
||||
discountAmount = point,
|
||||
),
|
||||
cardDetail = PaymentFixture.cardDetail(
|
||||
amount = commonPaymentRequest.amount,
|
||||
issuerCode = CardIssuerCode.SHINHAN
|
||||
)
|
||||
)
|
||||
|
||||
val cancelReason = "테스트입니다."
|
||||
val memberId = loginUtil.getUser().id!!
|
||||
|
||||
dummyInitializer.cancelPayment(
|
||||
memberId = memberId,
|
||||
reservationId = reservation.id,
|
||||
cancelReason = cancelReason,
|
||||
)
|
||||
|
||||
runDetailRetrieveTest(reservation).also {
|
||||
it["method"] shouldBe "간편결제"
|
||||
|
||||
with(it.get("detail") as LinkedHashMap<*, *>) {
|
||||
this["type"] shouldBe "CARD"
|
||||
this["issuerCode"] shouldBe "신한"
|
||||
this["easypayProviderName"] shouldBe "토스페이"
|
||||
this["easypayDiscountAmount"] shouldBe point
|
||||
}
|
||||
|
||||
with((it.get("cancel") as LinkedHashMap<*, *>)) {
|
||||
this["cancelReason"] shouldBe cancelReason
|
||||
this["canceledBy"] shouldBe memberId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("간편결제 선불충전금액 사용") {
|
||||
dummyInitializer.createPayment(
|
||||
reservationId = reservation.id,
|
||||
request = commonPaymentRequest,
|
||||
easyPayDetail = PaymentFixture.easypayDetail(
|
||||
amount = commonPaymentRequest.amount,
|
||||
provider = EasyPayCompanyCode.TOSSPAY,
|
||||
),
|
||||
)
|
||||
|
||||
runDetailRetrieveTest(reservation).also {
|
||||
it["method"] shouldBe "간편결제"
|
||||
|
||||
with(it.get("detail") as LinkedHashMap<*, *>) {
|
||||
this["type"] shouldBe "EASYPAY_PREPAID"
|
||||
this["providerName"] shouldBe "토스페이"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("계좌이체 사용") {
|
||||
dummyInitializer.createPayment(
|
||||
reservationId = reservation.id,
|
||||
request = commonPaymentRequest,
|
||||
transferDetail = PaymentFixture.transferDetail(
|
||||
bankCode = BankCode.SHINHAN
|
||||
)
|
||||
)
|
||||
|
||||
runDetailRetrieveTest(reservation).also {
|
||||
it["method"] shouldBe "계좌이체"
|
||||
|
||||
with(it.get("detail") as LinkedHashMap<*, *>) {
|
||||
this["type"] shouldBe "BANK_TRANSFER"
|
||||
this["bankName"] shouldBe "신한"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("예약이 없으면 실패한다.") {
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
on = {
|
||||
get("/reservations/$INVALID_PK/detail")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.NOT_FOUND.value())
|
||||
body("code", equalTo(ReservationErrorCode.RESERVATION_NOT_FOUND.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
test("예약은 있지만, 결제 정보가 없으면 실패한다.") {
|
||||
val reservation = dummyInitializer.createConfirmReservation(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
reserverToken = loginUtil.loginAsUser(),
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
on = {
|
||||
get("/reservations/${reservation.id}/detail")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.NOT_FOUND.value())
|
||||
body("code", equalTo(PaymentErrorCode.PAYMENT_NOT_FOUND.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
test("예약과 결제는 있지만, 결제 세부 내역이 없으면 실패한다.") {
|
||||
val reservation = dummyInitializer.createConfirmReservation(
|
||||
adminToken = loginUtil.loginAsAdmin(),
|
||||
reserverToken = loginUtil.loginAsUser(),
|
||||
)
|
||||
|
||||
dummyInitializer.createPayment(
|
||||
reservationId = reservation.id,
|
||||
easyPayDetail = PaymentFixture.easypayDetail(amount = 100_000)
|
||||
).also {
|
||||
paymentDetailRepository.deleteAll()
|
||||
}
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
on = {
|
||||
get("/reservations/${reservation.id}/detail")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.NOT_FOUND.value())
|
||||
body("code", equalTo(PaymentErrorCode.PAYMENT_DETAIL_NOT_FOUND.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun runDetailRetrieveTest(
|
||||
reservation: ReservationEntity
|
||||
): LinkedHashMap<String, Any> {
|
||||
return runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
on = {
|
||||
get("/reservations/${reservation.id}/detail")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
assertProperties(props = setOf("id", "member", "applicationDateTime", "payment"))
|
||||
}
|
||||
).also {
|
||||
it.extract().path<Long>("data.id") shouldBe reservation.id
|
||||
it.extract().path<Long>("data.member.id") shouldBe reservation.memberId
|
||||
}.extract().path("data.payment")
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user