generated from pricelees/issue-pr-template
730 lines
28 KiB
Kotlin
730 lines
28 KiB
Kotlin
package roomescape.reservation.web
|
|
|
|
import com.ninjasquad.springmockk.MockkBean
|
|
import com.ninjasquad.springmockk.SpykBean
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.matchers.shouldBe
|
|
import io.mockk.every
|
|
import io.restassured.module.kotlin.extensions.Given
|
|
import io.restassured.module.kotlin.extensions.Then
|
|
import io.restassured.module.kotlin.extensions.When
|
|
import jakarta.persistence.EntityManager
|
|
import org.hamcrest.Matchers.equalTo
|
|
import org.springframework.boot.test.context.SpringBootTest
|
|
import org.springframework.boot.test.web.server.LocalServerPort
|
|
import org.springframework.http.HttpStatus
|
|
import org.springframework.http.MediaType
|
|
import org.springframework.transaction.support.TransactionTemplate
|
|
import roomescape.auth.exception.AuthErrorCode
|
|
import roomescape.auth.infrastructure.jwt.JwtHandler
|
|
import roomescape.auth.web.support.MemberIdResolver
|
|
import roomescape.member.business.MemberService
|
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
|
import roomescape.member.infrastructure.persistence.Role
|
|
import roomescape.payment.exception.PaymentErrorCode
|
|
import roomescape.payment.exception.PaymentException
|
|
import roomescape.payment.infrastructure.client.TossPaymentClient
|
|
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
|
import roomescape.reservation.exception.ReservationErrorCode
|
|
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
|
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
|
import roomescape.theme.exception.ThemeErrorCode
|
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
|
import roomescape.time.infrastructure.persistence.TimeEntity
|
|
import roomescape.util.*
|
|
import java.time.LocalDate
|
|
import java.time.LocalTime
|
|
|
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|
class ReservationControllerTest(
|
|
@LocalServerPort val port: Int,
|
|
val entityManager: EntityManager,
|
|
val transactionTemplate: TransactionTemplate,
|
|
) : FunSpec({
|
|
extension(DatabaseCleanerExtension(mode = CleanerMode.AFTER_EACH_TEST))
|
|
}) {
|
|
@MockkBean
|
|
lateinit var paymentClient: TossPaymentClient
|
|
|
|
@SpykBean
|
|
lateinit var memberIdResolver: MemberIdResolver
|
|
|
|
@SpykBean
|
|
lateinit var memberService: MemberService
|
|
|
|
@MockkBean
|
|
lateinit var jwtHandler: JwtHandler
|
|
|
|
lateinit var testDataHelper: TestDataHelper
|
|
|
|
fun login(member: MemberEntity) {
|
|
every { jwtHandler.getMemberIdFromToken(any()) } returns member.id!!
|
|
every { memberService.findById(member.id!!) } returns member
|
|
every { memberIdResolver.resolveArgument(any(), any(), any(), any()) } returns member.id!!
|
|
}
|
|
|
|
init {
|
|
beforeSpec {
|
|
testDataHelper = TestDataHelper(entityManager, transactionTemplate)
|
|
}
|
|
|
|
context("POST /reservations") {
|
|
beforeTest {
|
|
val member = testDataHelper.createMember(role = Role.MEMBER)
|
|
login(member)
|
|
}
|
|
|
|
test("정상 응답") {
|
|
val reservationRequest = testDataHelper.createReservationRequest()
|
|
val paymentApproveResponse = PaymentFixture.createApproveResponse().copy(
|
|
paymentKey = reservationRequest.paymentKey,
|
|
orderId = reservationRequest.orderId,
|
|
totalAmount = reservationRequest.amount,
|
|
)
|
|
|
|
every { paymentClient.confirm(any()) } returns paymentApproveResponse
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(reservationRequest)
|
|
}.When {
|
|
post("/reservations")
|
|
}.Then {
|
|
statusCode(201)
|
|
body("data.date", equalTo(reservationRequest.date.toString()))
|
|
body("data.status", equalTo(ReservationStatus.CONFIRMED.name))
|
|
}
|
|
}
|
|
|
|
test("결제 과정에서 발생하는 에러는 그대로 응답") {
|
|
val reservationRequest = testDataHelper.createReservationRequest()
|
|
val paymentException = PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
|
|
|
|
every { paymentClient.confirm(any()) } throws paymentException
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(reservationRequest)
|
|
}.When {
|
|
post("/reservations")
|
|
}.Then {
|
|
statusCode(paymentException.errorCode.httpStatus.value())
|
|
body("code", equalTo(paymentException.errorCode.errorCode))
|
|
}
|
|
}
|
|
|
|
test("결제 완료 후 예약 / 결제 정보 저장 과정에서 에러 발생시 결제 취소 후 에러 응답을 받는다.") {
|
|
val reservationRequest = testDataHelper.createReservationRequest()
|
|
val paymentApproveResponse = PaymentFixture.createApproveResponse().copy(
|
|
paymentKey = reservationRequest.paymentKey,
|
|
orderId = reservationRequest.orderId,
|
|
totalAmount = reservationRequest.amount,
|
|
)
|
|
|
|
every { paymentClient.confirm(any()) } returns paymentApproveResponse
|
|
|
|
// 예약 저장 과정에서 테마가 없는 예외
|
|
val invalidRequest = reservationRequest.copy(themeId = reservationRequest.themeId + 1)
|
|
val expectedException = ThemeErrorCode.THEME_NOT_FOUND
|
|
|
|
every { paymentClient.cancel(any()) } returns PaymentFixture.createCancelResponse()
|
|
|
|
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
|
|
"SELECT COUNT(c) FROM CanceledPaymentEntity c",
|
|
Long::class.java
|
|
).singleResult
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(invalidRequest)
|
|
}.When {
|
|
post("/reservations")
|
|
}.Then {
|
|
statusCode(expectedException.httpStatus.value())
|
|
body("code", equalTo(expectedException.errorCode))
|
|
}
|
|
|
|
val canceledPaymentSizeAfterApiCall: Long = entityManager.createQuery(
|
|
"SELECT COUNT(c) FROM CanceledPaymentEntity c",
|
|
Long::class.java
|
|
).singleResult
|
|
|
|
canceledPaymentSizeAfterApiCall shouldBe canceledPaymentSizeBeforeApiCall + 1L
|
|
}
|
|
}
|
|
|
|
context("GET /reservations") {
|
|
lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = testDataHelper.createDummyReservations()
|
|
}
|
|
|
|
test("관리자이면 정상 응답") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations")
|
|
}.Then {
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("GET /reservations-mine") {
|
|
lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = testDataHelper.createDummyReservations()
|
|
}
|
|
|
|
test("로그인한 회원이 자신의 예약 목록을 조회한다.") {
|
|
val member = reservations.keys.first()
|
|
login(member)
|
|
val expectedReservations: Int = reservations[member]?.size ?: 0
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations-mine")
|
|
}.Then {
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(expectedReservations))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("GET /reservations/search") {
|
|
lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = testDataHelper.createDummyReservations()
|
|
}
|
|
|
|
test("관리자만 검색할 수 있다.") {
|
|
login(reservations.keys.first())
|
|
val expectedError = AuthErrorCode.ACCESS_DENIED
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
statusCode(expectedError.httpStatus.value())
|
|
body("code", equalTo(expectedError.errorCode))
|
|
}
|
|
}
|
|
|
|
test("파라미터를 지정하지 않으면 전체 목록 응답") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
|
}
|
|
}
|
|
|
|
test("시작 날짜가 종료 날짜 이전이면 예외 응답") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
|
|
val startDate = LocalDate.now().plusDays(1)
|
|
val endDate = LocalDate.now()
|
|
val expectedError = ReservationErrorCode.INVALID_SEARCH_DATE_RANGE
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
param("dateFrom", startDate.toString())
|
|
param("dateTo", endDate.toString())
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
statusCode(expectedError.httpStatus.value())
|
|
body("code", equalTo(expectedError.errorCode))
|
|
}
|
|
}
|
|
|
|
test("동일한 회원의 모든 예약 응답") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
val member = reservations.keys.first()
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
param("memberId", member.id)
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(reservations[member]?.size ?: 0))
|
|
}
|
|
}
|
|
|
|
test("동일한 테마의 모든 예약 응답") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
val themes = reservations.values.flatten().map { it.theme }
|
|
val requestThemeId: Long = themes.first().id!!
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
param("themeId", requestThemeId)
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(themes.count { it.id == requestThemeId }))
|
|
}
|
|
}
|
|
|
|
test("시작 날짜와 종료 날짜 사이의 예약 응답") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
val dateFrom: LocalDate = reservations.values.flatten().minOf { it.date }
|
|
val dateTo: LocalDate = reservations.values.flatten().maxOf { it.date }
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
param("dateFrom", dateFrom.toString())
|
|
param("dateTo", dateTo.toString())
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("DELETE /reservations/{id}") {
|
|
lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = testDataHelper.createDummyReservations()
|
|
}
|
|
|
|
test("관리자만 예약을 삭제할 수 있다.") {
|
|
login(testDataHelper.createMember(role = Role.MEMBER))
|
|
val reservation = reservations.values.flatten().first()
|
|
val expectedError = AuthErrorCode.ACCESS_DENIED
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
delete("/reservations/${reservation.id}")
|
|
}.Then {
|
|
statusCode(expectedError.httpStatus.value())
|
|
body("code", equalTo(expectedError.errorCode))
|
|
}
|
|
}
|
|
|
|
test("결제되지 않은 예약은 바로 제거") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
val reservationId = reservations.values.flatten().first().id!!
|
|
|
|
transactionTemplate.executeWithoutResult {
|
|
val reservation = entityManager.find(ReservationEntity::class.java, reservationId)
|
|
reservation.status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
|
}
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
delete("/reservations/$reservationId")
|
|
}.Then {
|
|
statusCode(HttpStatus.NO_CONTENT.value())
|
|
}
|
|
|
|
val deletedReservation = transactionTemplate.execute {
|
|
entityManager.find(ReservationEntity::class.java, reservationId)
|
|
}
|
|
deletedReservation shouldBe null
|
|
}
|
|
|
|
test("결제된 예약은 취소 후 제거") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
val reservation = reservations.values.flatten().first { it.status == ReservationStatus.CONFIRMED }
|
|
testDataHelper.createPayment(reservation)
|
|
|
|
every { paymentClient.cancel(any()) } returns PaymentFixture.createCancelResponse()
|
|
|
|
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
|
|
"SELECT COUNT(c) FROM CanceledPaymentEntity c",
|
|
Long::class.java
|
|
).singleResult
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
delete("/reservations/${reservation.id}")
|
|
}.Then {
|
|
statusCode(HttpStatus.NO_CONTENT.value())
|
|
}
|
|
|
|
val canceledPaymentSizeAfterApiCall: Long = entityManager.createQuery(
|
|
"SELECT COUNT(c) FROM CanceledPaymentEntity c",
|
|
Long::class.java
|
|
).singleResult
|
|
|
|
canceledPaymentSizeAfterApiCall shouldBe canceledPaymentSizeBeforeApiCall + 1L
|
|
}
|
|
}
|
|
|
|
context("POST /reservations/admin") {
|
|
test("관리자가 예약을 추가하면 결제 대기 상태로 예약 생성") {
|
|
val admin = testDataHelper.createMember(role = Role.ADMIN)
|
|
login(admin)
|
|
val theme = testDataHelper.createTheme()
|
|
val time = testDataHelper.createTime()
|
|
|
|
val adminRequest = AdminReservationCreateRequest(
|
|
date = LocalDate.now().plusDays(1),
|
|
themeId = theme.id!!,
|
|
timeId = time.id!!,
|
|
memberId = admin.id!!,
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(adminRequest)
|
|
}.When {
|
|
post("/reservations/admin")
|
|
}.Then {
|
|
statusCode(201)
|
|
body("data.status", equalTo(ReservationStatus.CONFIRMED_PAYMENT_REQUIRED.name))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("GET /reservations/waiting") {
|
|
lateinit var reservations: Map<MemberEntity, List<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = testDataHelper.createDummyReservations(reservationCount = 5)
|
|
}
|
|
|
|
test("관리자가 아니면 조회할 수 없다.") {
|
|
login(testDataHelper.createMember(role = Role.MEMBER))
|
|
val expectedError = AuthErrorCode.ACCESS_DENIED
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations/waiting")
|
|
}.Then {
|
|
statusCode(expectedError.httpStatus.value())
|
|
body("code", equalTo(expectedError.errorCode))
|
|
}
|
|
}
|
|
|
|
test("대기 중인 예약 목록을 조회한다.") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
val expected = reservations.values.flatten()
|
|
.count { it.status == ReservationStatus.WAITING }
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations/waiting")
|
|
}.Then {
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(expected))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("POST /reservations/waiting") {
|
|
test("회원이 대기 예약을 추가한다.") {
|
|
val member = testDataHelper.createMember(role = Role.MEMBER)
|
|
login(member)
|
|
val theme = testDataHelper.createTheme()
|
|
val time = testDataHelper.createTime()
|
|
|
|
val waitingCreateRequest = WaitingCreateRequest(
|
|
date = LocalDate.now().plusDays(1),
|
|
themeId = theme.id!!,
|
|
timeId = time.id!!
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(waitingCreateRequest)
|
|
}.When {
|
|
post("/reservations/waiting")
|
|
}.Then {
|
|
statusCode(201)
|
|
body("data.member.id", equalTo(member.id!!))
|
|
body("data.status", equalTo(ReservationStatus.WAITING.name))
|
|
}
|
|
}
|
|
|
|
test("이미 예약된 시간, 테마로 대기 예약 요청 시 예외 응답") {
|
|
val member = testDataHelper.createMember(role = Role.MEMBER)
|
|
login(member)
|
|
val theme = testDataHelper.createTheme()
|
|
val time = testDataHelper.createTime()
|
|
val date = LocalDate.now().plusDays(1)
|
|
|
|
testDataHelper.createReservation(
|
|
date = date,
|
|
theme = theme,
|
|
time = time,
|
|
member = member,
|
|
status = ReservationStatus.CONFIRMED
|
|
)
|
|
|
|
val waitingCreateRequest = WaitingCreateRequest(
|
|
date = date,
|
|
themeId = theme.id!!,
|
|
timeId = time.id!!
|
|
)
|
|
val expectedError = ReservationErrorCode.ALREADY_RESERVE
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(waitingCreateRequest)
|
|
}.When {
|
|
post("/reservations/waiting")
|
|
}.Then {
|
|
statusCode(expectedError.httpStatus.value())
|
|
body("code", equalTo(expectedError.errorCode))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("DELETE /reservations/waiting/{id}") {
|
|
test("대기 중인 예약을 취소한다.") {
|
|
val member = testDataHelper.createMember(role = Role.MEMBER)
|
|
login(member)
|
|
val waiting = testDataHelper.createReservation(
|
|
member = member,
|
|
status = ReservationStatus.WAITING
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
delete("/reservations/waiting/${waiting.id}")
|
|
}.Then {
|
|
statusCode(HttpStatus.NO_CONTENT.value())
|
|
}
|
|
|
|
val deleted = transactionTemplate.execute {
|
|
entityManager.find(ReservationEntity::class.java, waiting.id)
|
|
}
|
|
deleted shouldBe null
|
|
}
|
|
|
|
test("이미 확정된 예약을 삭제하면 예외 응답") {
|
|
val member = testDataHelper.createMember(role = Role.MEMBER)
|
|
login(member)
|
|
val reservation = testDataHelper.createReservation(
|
|
member = member,
|
|
status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
|
)
|
|
|
|
val expectedError = ReservationErrorCode.ALREADY_CONFIRMED
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
delete("/reservations/waiting/${reservation.id}")
|
|
}.Then {
|
|
statusCode(expectedError.httpStatus.value())
|
|
body("code", equalTo(expectedError.errorCode))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("POST /reservations/waiting/{id}/confirm") {
|
|
test("관리자만 승인할 수 있다.") {
|
|
login(testDataHelper.createMember(role = Role.MEMBER))
|
|
val expectedError = AuthErrorCode.ACCESS_DENIED
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
post("/reservations/waiting/1/confirm")
|
|
}.Then {
|
|
statusCode(expectedError.httpStatus.value())
|
|
body("code", equalTo(expectedError.errorCode))
|
|
}
|
|
}
|
|
|
|
test("대기 예약을 승인하면 결제 대기 상태로 변경") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
val reservation = testDataHelper.createReservation(
|
|
status = ReservationStatus.WAITING
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
post("/reservations/waiting/${reservation.id!!}/confirm")
|
|
}.Then {
|
|
statusCode(200)
|
|
}
|
|
|
|
val updatedReservation = transactionTemplate.execute {
|
|
entityManager.find(ReservationEntity::class.java, reservation.id)
|
|
}
|
|
updatedReservation?.status shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
|
}
|
|
|
|
test("다른 확정된 예약을 승인하면 예외 응답") {
|
|
val admin = testDataHelper.createMember(role = Role.ADMIN)
|
|
login(admin)
|
|
val alreadyReserved = testDataHelper.createReservation(
|
|
member = admin,
|
|
status = ReservationStatus.CONFIRMED
|
|
)
|
|
|
|
val member = testDataHelper.createMember(role = Role.MEMBER)
|
|
val waiting = testDataHelper.createReservation(
|
|
date = alreadyReserved.date,
|
|
time = alreadyReserved.time,
|
|
theme = alreadyReserved.theme,
|
|
member = member,
|
|
status = ReservationStatus.WAITING
|
|
)
|
|
|
|
val expectedError = ReservationErrorCode.CONFIRMED_RESERVATION_ALREADY_EXISTS
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
post("/reservations/waiting/${waiting.id!!}/confirm")
|
|
}.Then {
|
|
statusCode(expectedError.httpStatus.value())
|
|
body("code", equalTo(expectedError.errorCode))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("POST /reservations/waiting/{id}/reject") {
|
|
test("관리자만 거절할 수 있다.") {
|
|
login(testDataHelper.createMember(role = Role.MEMBER))
|
|
val expectedError = AuthErrorCode.ACCESS_DENIED
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
post("/reservations/waiting/1/reject")
|
|
}.Then {
|
|
statusCode(expectedError.httpStatus.value())
|
|
body("code", equalTo(expectedError.errorCode))
|
|
}
|
|
}
|
|
|
|
test("거절된 예약은 삭제된다.") {
|
|
login(testDataHelper.createMember(role = Role.ADMIN))
|
|
val reservation = testDataHelper.createReservation(
|
|
status = ReservationStatus.WAITING
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
post("/reservations/waiting/${reservation.id!!}/reject")
|
|
}.Then {
|
|
statusCode(204)
|
|
}
|
|
|
|
val rejected = transactionTemplate.execute {
|
|
entityManager.find(ReservationEntity::class.java, reservation.id)
|
|
}
|
|
rejected shouldBe null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class TestDataHelper(
|
|
private val entityManager: EntityManager,
|
|
private val transactionTemplate: TransactionTemplate,
|
|
) {
|
|
private var memberSequence = 0L
|
|
private var themeSequence = 0L
|
|
private var timeSequence = 0L
|
|
|
|
fun createMember(
|
|
role: Role = Role.MEMBER,
|
|
account: String = "member${++memberSequence}@test.com",
|
|
): MemberEntity {
|
|
val member = MemberFixture.create(role = role, account = account)
|
|
return persist(member)
|
|
}
|
|
|
|
fun createTheme(name: String = "theme-${++themeSequence}"): ThemeEntity {
|
|
val theme = ThemeFixture.create(name = name)
|
|
return persist(theme)
|
|
}
|
|
|
|
fun createTime(startAt: LocalTime = LocalTime.of(10, 0).plusMinutes(++timeSequence * 10)): TimeEntity {
|
|
val time = TimeFixture.create(startAt = startAt)
|
|
return persist(time)
|
|
}
|
|
|
|
fun createReservation(
|
|
date: LocalDate = LocalDate.now().plusDays(1),
|
|
theme: ThemeEntity = createTheme(),
|
|
time: TimeEntity = createTime(),
|
|
member: MemberEntity = createMember(),
|
|
status: ReservationStatus = ReservationStatus.CONFIRMED,
|
|
): ReservationEntity {
|
|
val reservation = ReservationFixture.create(
|
|
date = date,
|
|
theme = theme,
|
|
time = time,
|
|
member = member,
|
|
status = status
|
|
)
|
|
return persist(reservation)
|
|
}
|
|
|
|
fun createPayment(reservation: ReservationEntity): PaymentEntity {
|
|
val payment = PaymentFixture.create(reservation = reservation)
|
|
return persist(payment)
|
|
}
|
|
|
|
fun createReservationRequest(
|
|
theme: ThemeEntity = createTheme(),
|
|
time: TimeEntity = createTime(),
|
|
): ReservationCreateWithPaymentRequest {
|
|
return ReservationFixture.createRequest(
|
|
themeId = theme.id!!,
|
|
timeId = time.id!!,
|
|
)
|
|
}
|
|
|
|
fun createDummyReservations(
|
|
memberCount: Int = 2,
|
|
reservationCount: Int = 10,
|
|
): Map<MemberEntity, List<ReservationEntity>> {
|
|
val members = (1..memberCount).map { createMember(role = Role.MEMBER) }
|
|
val reservations = (1..reservationCount).map { index ->
|
|
createReservation(
|
|
member = members[index % memberCount],
|
|
status = ReservationStatus.CONFIRMED
|
|
)
|
|
}
|
|
return reservations.groupBy { it.member }
|
|
}
|
|
|
|
private fun <T> persist(entity: T): T {
|
|
transactionTemplate.executeWithoutResult {
|
|
entityManager.persist(entity)
|
|
}
|
|
return entity
|
|
}
|
|
} |