generated from pricelees/issue-pr-template
793 lines
30 KiB
Kotlin
793 lines
30 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.containsString
|
|
import org.hamcrest.Matchers.equalTo
|
|
import org.springframework.boot.test.context.SpringBootTest
|
|
import org.springframework.boot.test.web.server.LocalServerPort
|
|
import org.springframework.http.HttpHeaders
|
|
import org.springframework.http.HttpStatus
|
|
import org.springframework.http.MediaType
|
|
import org.springframework.transaction.support.TransactionTemplate
|
|
import roomescape.auth.web.support.AdminInterceptor
|
|
import roomescape.auth.web.support.LoginInterceptor
|
|
import roomescape.auth.web.support.MemberIdResolver
|
|
import roomescape.common.exception.ErrorType
|
|
import roomescape.common.exception.RoomescapeException
|
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
|
import roomescape.member.infrastructure.persistence.Role
|
|
import roomescape.payment.infrastructure.client.TossPaymentClient
|
|
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
|
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
|
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
|
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
|
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 loginInterceptor: LoginInterceptor
|
|
|
|
@SpykBean
|
|
lateinit var adminInterceptor: AdminInterceptor
|
|
|
|
@SpykBean
|
|
lateinit var memberIdResolver: MemberIdResolver
|
|
|
|
init {
|
|
context("POST /reservations") {
|
|
lateinit var member: MemberEntity
|
|
beforeTest {
|
|
member = login(MemberFixture.create(role = Role.MEMBER))
|
|
}
|
|
|
|
test("정상 응답") {
|
|
val reservationRequest = createRequest()
|
|
val paymentApproveResponse = PaymentFixture.createApproveResponse().copy(
|
|
paymentKey = reservationRequest.paymentKey,
|
|
orderId = reservationRequest.orderId,
|
|
totalAmount = reservationRequest.amount,
|
|
)
|
|
|
|
every {
|
|
paymentClient.confirmPayment(any())
|
|
} returns paymentApproveResponse
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(reservationRequest)
|
|
}.When {
|
|
post("/reservations")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(201)
|
|
body("data.date", equalTo(reservationRequest.date.toString()))
|
|
body("data.status", equalTo(ReservationStatus.CONFIRMED.name))
|
|
}
|
|
}
|
|
|
|
test("결제 과정에서 발생하는 에러는 그대로 응답") {
|
|
val reservationRequest = createRequest()
|
|
val paymentException = RoomescapeException(
|
|
ErrorType.PAYMENT_SERVER_ERROR,
|
|
HttpStatus.INTERNAL_SERVER_ERROR
|
|
)
|
|
|
|
every {
|
|
paymentClient.confirmPayment(any())
|
|
} throws paymentException
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(reservationRequest)
|
|
}.When {
|
|
post("/reservations")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(paymentException.httpStatus.value())
|
|
body("errorType", equalTo(paymentException.errorType.name))
|
|
}
|
|
}
|
|
|
|
test("결제 완료 후 예약 / 결제 정보 저장 과정에서 에러 발생시 결제 취소 후 에러 응답") {
|
|
val reservationRequest = createRequest()
|
|
val paymentApproveResponse = PaymentFixture.createApproveResponse().copy(
|
|
paymentKey = reservationRequest.paymentKey,
|
|
orderId = reservationRequest.orderId,
|
|
totalAmount = reservationRequest.amount,
|
|
)
|
|
|
|
every {
|
|
paymentClient.confirmPayment(any())
|
|
} returns paymentApproveResponse
|
|
|
|
// 예약 저장 과정에서 테마가 없는 예외
|
|
val invalidRequest = reservationRequest.copy(themeId = reservationRequest.themeId + 1)
|
|
val expectedException = RoomescapeException(ErrorType.THEME_NOT_FOUND, HttpStatus.BAD_REQUEST)
|
|
|
|
every {
|
|
paymentClient.cancelPayment(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 {
|
|
log().all()
|
|
statusCode(expectedException.httpStatus.value())
|
|
body("errorType", equalTo(expectedException.errorType.name))
|
|
}
|
|
|
|
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: MutableMap<MemberEntity, MutableList<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = createDummyReservations()
|
|
}
|
|
|
|
test("관리자이면 정상 응답") {
|
|
login(MemberFixture.create(role = Role.ADMIN))
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("GET /reservations-mine") {
|
|
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = createDummyReservations()
|
|
}
|
|
|
|
test("로그인한 회원이 자신의 예약 목록을 조회한다.") {
|
|
val member: MemberEntity = login(reservations.keys.first())
|
|
val expectedReservations: Int = reservations[member]?.size ?: 0
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations-mine")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(expectedReservations))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("GET /reservations/search") {
|
|
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = createDummyReservations()
|
|
}
|
|
|
|
test("관리자만 검색할 수 있다.") {
|
|
login(reservations.keys.first())
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
log().all()
|
|
header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE))
|
|
}
|
|
}
|
|
|
|
test("파라미터를 지정하지 않으면 전체 목록 응답") {
|
|
login(MemberFixture.create(role = Role.ADMIN))
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
|
}
|
|
}
|
|
|
|
test("시작 날짜가 종료 날짜 이전이면 예외 응답") {
|
|
login(MemberFixture.create(role = Role.ADMIN))
|
|
|
|
val startDate = LocalDate.now().plusDays(1)
|
|
val endDate = LocalDate.now()
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
param("dateFrom", startDate.toString())
|
|
param("dateTo", endDate.toString())
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(HttpStatus.BAD_REQUEST.value())
|
|
body("errorType", equalTo(ErrorType.INVALID_DATE_RANGE.name))
|
|
}
|
|
}
|
|
|
|
test("동일한 회원의 모든 예약 응답") {
|
|
login(MemberFixture.create(role = Role.ADMIN))
|
|
val member: MemberEntity = reservations.keys.first()
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
param("memberId", member.id)
|
|
}.When {
|
|
get("/reservations/search")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(reservations[member]?.size ?: 0))
|
|
}
|
|
}
|
|
|
|
test("동일한 테마의 모든 예약 응답") {
|
|
login(MemberFixture.create(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 {
|
|
log().all()
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(themes.filter { it.id == requestThemeId }.size))
|
|
}
|
|
}
|
|
|
|
test("시작 날짜와 종료 날짜 사이의 예약 응답") {
|
|
login(MemberFixture.create(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 {
|
|
log().all()
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("DELETE /reservations/{id}") {
|
|
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = createDummyReservations()
|
|
}
|
|
|
|
test("관리자만 예약을 삭제할 수 있다.") {
|
|
login(MemberFixture.create(role = Role.MEMBER))
|
|
val reservation: ReservationEntity = reservations.values.flatten().first()
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
delete("/reservations/${reservation.id}")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(302)
|
|
header(HttpHeaders.LOCATION, containsString("/login"))
|
|
}
|
|
}
|
|
|
|
test("결제되지 않은 예약은 바로 제거") {
|
|
login(MemberFixture.create(role = Role.ADMIN))
|
|
val reservationId: Long = reservations.values.flatten().first().id!!
|
|
|
|
transactionTemplate.execute {
|
|
val reservation: ReservationEntity = entityManager.find(
|
|
ReservationEntity::class.java,
|
|
reservationId
|
|
)
|
|
reservation.reservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
|
entityManager.persist(reservation)
|
|
entityManager.flush()
|
|
entityManager.clear()
|
|
}
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
delete("/reservations/$reservationId")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(HttpStatus.NO_CONTENT.value())
|
|
}
|
|
|
|
// 예약이 삭제되었는지 확인
|
|
transactionTemplate.executeWithoutResult {
|
|
val deletedReservation = entityManager.find(
|
|
ReservationEntity::class.java,
|
|
reservationId
|
|
)
|
|
deletedReservation shouldBe null
|
|
}
|
|
}
|
|
|
|
test("결제된 예약은 취소 후 제거") {
|
|
login(MemberFixture.create(role = Role.ADMIN))
|
|
val reservation: ReservationEntity = reservations.values.flatten().first()
|
|
lateinit var payment: PaymentEntity
|
|
|
|
transactionTemplate.execute {
|
|
payment = PaymentFixture.create(reservation = reservation).also {
|
|
entityManager.persist(it)
|
|
entityManager.flush()
|
|
entityManager.clear()
|
|
}
|
|
}
|
|
|
|
every {
|
|
paymentClient.cancelPayment(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 {
|
|
log().all()
|
|
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 member = login(MemberFixture.create(role = Role.ADMIN))
|
|
val adminRequest: AdminReservationRequest = createRequest().let {
|
|
AdminReservationRequest(
|
|
date = it.date,
|
|
themeId = it.themeId,
|
|
timeId = it.timeId,
|
|
memberId = member.id!!,
|
|
)
|
|
}
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(adminRequest)
|
|
}.When {
|
|
post("/reservations/admin")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(201)
|
|
body("data.status", equalTo(ReservationStatus.CONFIRMED_PAYMENT_REQUIRED.name))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("GET /reservations/waiting") {
|
|
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = createDummyReservations()
|
|
}
|
|
|
|
test("관리자가 아니면 조회할 수 없다.") {
|
|
login(MemberFixture.create(role = Role.MEMBER))
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations/waiting")
|
|
}.Then {
|
|
log().all()
|
|
header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE))
|
|
}
|
|
}
|
|
|
|
test("대기 중인 예약 목록을 조회한다.") {
|
|
login(MemberFixture.create(role = Role.ADMIN))
|
|
val expected = reservations.values.flatten()
|
|
.count { it.reservationStatus == ReservationStatus.WAITING }
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
}.When {
|
|
get("/reservations/waiting")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(200)
|
|
body("data.reservations.size()", equalTo(expected))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("POST /reservations/waiting") {
|
|
test("회원이 대기 예약을 추가한다.") {
|
|
val member = login(MemberFixture.create(role = Role.MEMBER))
|
|
val waitingRequest: WaitingRequest = createRequest().let {
|
|
WaitingRequest(
|
|
date = it.date,
|
|
themeId = it.themeId,
|
|
timeId = it.timeId
|
|
)
|
|
}
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(waitingRequest)
|
|
}.When {
|
|
post("/reservations/waiting")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(201)
|
|
body("data.member.id", equalTo(member.id!!.toInt()))
|
|
body("data.status", equalTo(ReservationStatus.WAITING.name))
|
|
}
|
|
}
|
|
|
|
test("이미 예약된 시간, 테마로 대기 예약 요청 시 예외 응답") {
|
|
val member = login(MemberFixture.create(role = Role.MEMBER))
|
|
val reservationRequest = createRequest()
|
|
|
|
transactionTemplate.executeWithoutResult {
|
|
val reservation = ReservationFixture.create(
|
|
date = reservationRequest.date,
|
|
theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId),
|
|
time = entityManager.find(TimeEntity::class.java, reservationRequest.timeId),
|
|
member = member,
|
|
status = ReservationStatus.WAITING
|
|
)
|
|
entityManager.persist(reservation)
|
|
entityManager.flush()
|
|
entityManager.clear()
|
|
}
|
|
|
|
// 이미 예약된 시간, 테마로 대기 예약 요청
|
|
val waitingRequest = WaitingRequest(
|
|
date = reservationRequest.date,
|
|
themeId = reservationRequest.themeId,
|
|
timeId = reservationRequest.timeId
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
|
body(waitingRequest)
|
|
}.When {
|
|
post("/reservations/waiting")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(HttpStatus.BAD_REQUEST.value())
|
|
body("errorType", equalTo(ErrorType.HAS_RESERVATION_OR_WAITING.name))
|
|
}
|
|
}
|
|
}
|
|
|
|
context("DELETE /reservations/waiting/{id}") {
|
|
lateinit var reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>>
|
|
beforeTest {
|
|
reservations = createDummyReservations()
|
|
}
|
|
|
|
test("대기 중인 예약을 취소한다.") {
|
|
val member = login(MemberFixture.create(role = Role.MEMBER))
|
|
val waiting: ReservationEntity = createSingleReservation(
|
|
member = member,
|
|
status = ReservationStatus.WAITING
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
delete("/reservations/waiting/${waiting.id}")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(HttpStatus.NO_CONTENT.value())
|
|
}
|
|
|
|
transactionTemplate.executeWithoutResult { _ ->
|
|
entityManager.find(
|
|
ReservationEntity::class.java,
|
|
waiting.id
|
|
) shouldBe null
|
|
}
|
|
}
|
|
|
|
test("이미 완료된 예약은 삭제할 수 없다.") {
|
|
val member = login(MemberFixture.create(role = Role.MEMBER))
|
|
val reservation: ReservationEntity = createSingleReservation(
|
|
member = member,
|
|
status = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
delete("/reservations/waiting/{id}", reservation.id)
|
|
}.Then {
|
|
log().all()
|
|
body("errorType", equalTo(ErrorType.RESERVATION_NOT_FOUND.name))
|
|
statusCode(HttpStatus.NOT_FOUND.value())
|
|
}
|
|
}
|
|
}
|
|
|
|
context("POST /reservations/waiting/{id}/approve") {
|
|
test("관리자만 승인할 수 있다.") {
|
|
login(MemberFixture.create(role = Role.MEMBER))
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
post("/reservations/waiting/1/approve")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(302)
|
|
header(HttpHeaders.LOCATION, containsString("/login"))
|
|
}
|
|
}
|
|
|
|
test("대기 예약을 승인하면 결제 대기 상태로 변경") {
|
|
val member = login(MemberFixture.create(role = Role.ADMIN))
|
|
val reservation = createSingleReservation(
|
|
member = member,
|
|
status = ReservationStatus.WAITING
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
post("/reservations/waiting/${reservation.id!!}/approve")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(200)
|
|
}
|
|
|
|
transactionTemplate.executeWithoutResult { _ ->
|
|
entityManager.find(
|
|
ReservationEntity::class.java,
|
|
reservation.id
|
|
)?.also {
|
|
it.reservationStatus shouldBe ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
|
} ?: throw AssertionError("Reservation not found")
|
|
}
|
|
}
|
|
}
|
|
|
|
context("POST /reservations/waiting/{id}/deny") {
|
|
test("관리자만 거절할 수 있다.") {
|
|
login(MemberFixture.create(role = Role.MEMBER))
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
post("/reservations/waiting/1/deny")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(302)
|
|
header(HttpHeaders.LOCATION, containsString("/login"))
|
|
}
|
|
}
|
|
|
|
test("거절된 예약은 삭제된다.") {
|
|
val member = login(MemberFixture.create(role = Role.ADMIN))
|
|
val reservation = createSingleReservation(
|
|
member = member,
|
|
status = ReservationStatus.WAITING
|
|
)
|
|
|
|
Given {
|
|
port(port)
|
|
}.When {
|
|
post("/reservations/waiting/${reservation.id!!}/deny")
|
|
}.Then {
|
|
log().all()
|
|
statusCode(204)
|
|
}
|
|
|
|
transactionTemplate.executeWithoutResult { _ ->
|
|
entityManager.find(
|
|
ReservationEntity::class.java,
|
|
reservation.id
|
|
) shouldBe null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun createSingleReservation(
|
|
date: LocalDate = LocalDate.now().plusDays(1),
|
|
time: LocalTime = LocalTime.now(),
|
|
themeName: String = "Default Theme",
|
|
member: MemberEntity = MemberFixture.create(role = Role.MEMBER),
|
|
status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
|
|
): ReservationEntity {
|
|
return ReservationFixture.create(
|
|
date = date,
|
|
theme = ThemeFixture.create(name = themeName),
|
|
time = TimeFixture.create(startAt = time),
|
|
member = member,
|
|
status = status
|
|
).also { it ->
|
|
transactionTemplate.execute { _ ->
|
|
if (member.id == null) {
|
|
entityManager.persist(member)
|
|
}
|
|
entityManager.persist(it.time)
|
|
entityManager.persist(it.theme)
|
|
entityManager.persist(it)
|
|
entityManager.flush()
|
|
entityManager.clear()
|
|
}
|
|
}
|
|
}
|
|
|
|
fun createDummyReservations(): MutableMap<MemberEntity, MutableList<ReservationEntity>> {
|
|
val reservations: MutableMap<MemberEntity, MutableList<ReservationEntity>> = mutableMapOf()
|
|
val members: List<MemberEntity> = listOf(
|
|
MemberFixture.create(role = Role.MEMBER),
|
|
MemberFixture.create(role = Role.MEMBER)
|
|
)
|
|
|
|
transactionTemplate.executeWithoutResult {
|
|
members.forEach { member ->
|
|
entityManager.persist(member)
|
|
}
|
|
entityManager.flush()
|
|
entityManager.clear()
|
|
}
|
|
|
|
transactionTemplate.executeWithoutResult {
|
|
repeat(10) { index ->
|
|
val theme = ThemeFixture.create(name = "theme$index")
|
|
val time = TimeFixture.create(startAt = LocalTime.now().plusMinutes(index.toLong()))
|
|
entityManager.persist(theme)
|
|
entityManager.persist(time)
|
|
|
|
val reservation = ReservationFixture.create(
|
|
date = LocalDate.now().plusDays(index.toLong()),
|
|
theme = theme,
|
|
time = time,
|
|
member = members[index % members.size],
|
|
status = ReservationStatus.CONFIRMED
|
|
)
|
|
entityManager.persist(reservation)
|
|
reservations.getOrPut(reservation.member) { mutableListOf() }.add(reservation)
|
|
}
|
|
entityManager.flush()
|
|
entityManager.clear()
|
|
}
|
|
|
|
return reservations
|
|
}
|
|
|
|
fun createRequest(
|
|
theme: ThemeEntity = ThemeFixture.create(),
|
|
time: TimeEntity = TimeFixture.create(),
|
|
): ReservationRequest {
|
|
lateinit var reservationRequest: ReservationRequest
|
|
|
|
transactionTemplate.executeWithoutResult {
|
|
entityManager.persist(theme)
|
|
entityManager.persist(time)
|
|
|
|
reservationRequest = ReservationFixture.createRequest(
|
|
themeId = theme.id!!,
|
|
timeId = time.id!!,
|
|
)
|
|
|
|
entityManager.flush()
|
|
entityManager.clear()
|
|
}
|
|
|
|
return reservationRequest
|
|
}
|
|
|
|
fun login(member: MemberEntity): MemberEntity {
|
|
if (member.id == null) {
|
|
transactionTemplate.executeWithoutResult {
|
|
entityManager.persist(member)
|
|
entityManager.flush()
|
|
entityManager.clear()
|
|
}
|
|
}
|
|
|
|
if (member.isAdmin()) {
|
|
loginAsAdmin()
|
|
} else {
|
|
loginAsUser()
|
|
}
|
|
resolveMemberId(member.id!!)
|
|
|
|
return member
|
|
}
|
|
|
|
private fun loginAsUser() {
|
|
every {
|
|
loginInterceptor.preHandle(any(), any(), any())
|
|
} returns true
|
|
}
|
|
|
|
private fun loginAsAdmin() {
|
|
every {
|
|
adminInterceptor.preHandle(any(), any(), any())
|
|
} returns true
|
|
}
|
|
|
|
private fun resolveMemberId(memberId: Long) {
|
|
every {
|
|
memberIdResolver.resolveArgument(any(), any(), any(), any())
|
|
} returns memberId
|
|
}
|
|
}
|