generated from pricelees/issue-pr-template
<!-- 제목 양식 --> <!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) --> ## 📝 관련 이슈 및 PR **PR과 관련된 이슈 번호** - #18 ## ✨ 작업 내용 <!-- 어떤 작업을 했는지 알려주세요! --> - 기존 자바와의 호환성을 위해 사용하던 \@Jvm.. 어노테이션 및 팩토리 메서드 제거 - 기존에 get, find, save 등으로 산재되어 있던 메서드 컨벤션 통일 - 일부 API endpoint 수정 - 테이블 이름 단수 -> 복수 수정 추가적으로 개선이 필요한 점은 있지만, 이는 기능 개선 과정에서 수정할 예정 ## 🧪 테스트 <!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! --> 각 작업 마다 전체 테스트 수행 및 정상 동작 확인 ## 📚 참고 자료 및 기타 <!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! --> Reviewed-on: #19 Co-authored-by: pricelees <priceelees@gmail.com> Co-committed-by: pricelees <priceelees@gmail.com>
This commit is contained in:
parent
790c34cc3c
commit
9f8ee8cc02
@ -1,17 +0,0 @@
|
|||||||
package roomescape.common.dto.response
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
|
||||||
|
|
||||||
@Schema(name = "API 성공 응답")
|
|
||||||
@JvmRecord
|
|
||||||
data class RoomescapeApiResponse<T>(
|
|
||||||
val data: T? = null
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun <T> success(data: T): RoomescapeApiResponse<T> = RoomescapeApiResponse(data)
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun success(): RoomescapeApiResponse<Void> = RoomescapeApiResponse(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package roomescape.common.dto.response
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
|
||||||
import roomescape.common.exception.ErrorType
|
|
||||||
|
|
||||||
@Schema(name = "API 에러 응답")
|
|
||||||
@JvmRecord
|
|
||||||
data class RoomescapeErrorResponse(
|
|
||||||
val errorType: ErrorType,
|
|
||||||
val message: String
|
|
||||||
)
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
package roomescape.member.web
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
|
||||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
|
||||||
|
|
||||||
fun MemberEntity.toResponse(): MemberResponse = MemberResponse(
|
|
||||||
id = id!!,
|
|
||||||
name = name
|
|
||||||
)
|
|
||||||
|
|
||||||
@Schema(name = "회원 조회 응답", description = "회원 정보 조회 응답시 사용됩니다.")
|
|
||||||
data class MemberResponse(
|
|
||||||
@field:Schema(description = "회원의 고유 번호")
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:Schema(description = "회원의 이름")
|
|
||||||
val name: String
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun fromEntity(member: MemberEntity): MemberResponse {
|
|
||||||
return MemberResponse(member.id!!, member.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Schema(name = "회원 목록 조회 응답", description = "모든 회원의 정보 조회 응답시 사용됩니다.")
|
|
||||||
data class MembersResponse(
|
|
||||||
@field:Schema(description = "모든 회원의 ID 및 이름")
|
|
||||||
val members: List<MemberResponse>
|
|
||||||
)
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.client
|
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
data class TossPaymentErrorResponse(
|
|
||||||
val code: String,
|
|
||||||
val message: String
|
|
||||||
)
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
package roomescape.payment.web
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
|
||||||
import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer
|
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
|
||||||
import roomescape.reservation.web.ReservationResponse
|
|
||||||
import roomescape.reservation.web.toResponse
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
|
|
||||||
class PaymentApprove {
|
|
||||||
@JvmRecord
|
|
||||||
data class Request(
|
|
||||||
@JvmField val paymentKey: String,
|
|
||||||
@JvmField val orderId: String,
|
|
||||||
@JvmField val amount: Long,
|
|
||||||
@JvmField val paymentType: String
|
|
||||||
)
|
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
data class Response(
|
|
||||||
@JvmField val paymentKey: String,
|
|
||||||
@JvmField val orderId: String,
|
|
||||||
@JvmField val approvedAt: OffsetDateTime,
|
|
||||||
@JvmField val totalAmount: Long
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
class PaymentCancel {
|
|
||||||
@JvmRecord
|
|
||||||
data class Request(
|
|
||||||
@JvmField val paymentKey: String,
|
|
||||||
@JvmField val amount: Long,
|
|
||||||
@JvmField val cancelReason: String
|
|
||||||
)
|
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
@JsonDeserialize(using = PaymentCancelResponseDeserializer::class)
|
|
||||||
data class Response(
|
|
||||||
@JvmField val cancelStatus: String,
|
|
||||||
@JvmField val cancelReason: String,
|
|
||||||
@JvmField val cancelAmount: Long,
|
|
||||||
@JvmField val canceledAt: OffsetDateTime
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
data class ReservationPaymentResponse(
|
|
||||||
val id: Long,
|
|
||||||
val orderId: String,
|
|
||||||
val paymentKey: String,
|
|
||||||
val totalAmount: Long,
|
|
||||||
val reservation: ReservationResponse,
|
|
||||||
val approvedAt: OffsetDateTime
|
|
||||||
)
|
|
||||||
|
|
||||||
fun PaymentEntity.toReservationPaymentResponse(): ReservationPaymentResponse = ReservationPaymentResponse(
|
|
||||||
id = this.id!!,
|
|
||||||
orderId = this.orderId,
|
|
||||||
paymentKey = this.paymentKey,
|
|
||||||
totalAmount = this.totalAmount,
|
|
||||||
reservation = this.reservation.toResponse(),
|
|
||||||
approvedAt = this.approvedAt
|
|
||||||
)
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
package roomescape.reservation.business
|
|
||||||
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
|
||||||
import org.springframework.http.HttpStatus
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import org.springframework.transaction.annotation.Transactional
|
|
||||||
import roomescape.common.exception.ErrorType
|
|
||||||
import roomescape.common.exception.RoomescapeException
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
|
|
||||||
import roomescape.reservation.web.*
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class ReservationTimeService(
|
|
||||||
private val reservationTimeRepository: ReservationTimeRepository,
|
|
||||||
private val reservationRepository: ReservationRepository
|
|
||||||
) {
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
fun findTimeById(id: Long): ReservationTimeEntity = reservationTimeRepository.findByIdOrNull(id)
|
|
||||||
?: throw RoomescapeException(
|
|
||||||
ErrorType.RESERVATION_TIME_NOT_FOUND,
|
|
||||||
"[reservationTimeId: $id]",
|
|
||||||
HttpStatus.BAD_REQUEST
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
fun findAllTimes(): ReservationTimesResponse = reservationTimeRepository.findAll()
|
|
||||||
.toResponses()
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
fun addTime(reservationTimeRequest: ReservationTimeRequest): ReservationTimeResponse {
|
|
||||||
val startAt: LocalTime = reservationTimeRequest.startAt
|
|
||||||
|
|
||||||
if (reservationTimeRepository.existsByStartAt(startAt)) {
|
|
||||||
throw RoomescapeException(
|
|
||||||
ErrorType.TIME_DUPLICATED, "[startAt: $startAt]", HttpStatus.CONFLICT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ReservationTimeEntity(startAt = startAt)
|
|
||||||
.also { reservationTimeRepository.save(it) }
|
|
||||||
.toResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
fun removeTimeById(id: Long) {
|
|
||||||
val reservationTime: ReservationTimeEntity = findTimeById(id)
|
|
||||||
reservationRepository.findByReservationTime(reservationTime)
|
|
||||||
.also {
|
|
||||||
if (it.isNotEmpty()) {
|
|
||||||
throw RoomescapeException(
|
|
||||||
ErrorType.TIME_IS_USED_CONFLICT, "[timeId: $id]", HttpStatus.CONFLICT
|
|
||||||
)
|
|
||||||
}
|
|
||||||
reservationTimeRepository.deleteById(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
fun findAllAvailableTimesByDateAndTheme(date: LocalDate, themeId: Long): ReservationTimeInfosResponse {
|
|
||||||
val allTimes = reservationTimeRepository.findAll()
|
|
||||||
val reservations: List<ReservationEntity> = reservationRepository.findByDateAndThemeId(date, themeId)
|
|
||||||
|
|
||||||
return ReservationTimeInfosResponse(allTimes.map { time ->
|
|
||||||
val alreadyBooked: Boolean = reservations.any { reservation -> reservation.reservationTime.id == time.id }
|
|
||||||
time.toInfoResponse(alreadyBooked)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
package roomescape.reservation.web
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
|
||||||
import roomescape.payment.web.PaymentApprove
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
@Schema(name = "관리자 예약 저장 요청", description = "관리자의 예약 저장 요청시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class AdminReservationRequest(
|
|
||||||
@JvmField @field:Schema(description = "예약 날짜. 지난 날짜는 지정할 수 없으며, yyyy-MM-dd 형식으로 입력해야 합니다.", type = "string", example = "2022-12-31")
|
|
||||||
val date: LocalDate,
|
|
||||||
@JvmField @field:Schema(description = "예약 시간 ID.", example = "1")
|
|
||||||
val timeId: Long,
|
|
||||||
@JvmField @field:Schema(description = "테마 ID", example = "1")
|
|
||||||
val themeId: Long,
|
|
||||||
@JvmField @field:Schema(description = "회원 ID", example = "1")
|
|
||||||
val memberId: Long
|
|
||||||
)
|
|
||||||
|
|
||||||
@Schema(name = "회원의 예약 저장 요청", description = "회원의 예약 요청시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class ReservationRequest(
|
|
||||||
@JvmField
|
|
||||||
@field:Schema(description = "예약 날짜. 지난 날짜는 지정할 수 없으며, yyyy-MM-dd 형식으로 입력해야 합니다.", type = "string", example = "2022-12-31")
|
|
||||||
val date: LocalDate,
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@field:Schema(description = "예약 시간 ID.", example = "1")
|
|
||||||
val timeId: Long,
|
|
||||||
|
|
||||||
@JvmField @field:Schema(description = "테마 ID", example = "1")
|
|
||||||
val themeId: Long,
|
|
||||||
|
|
||||||
@field:Schema(description = "결제 위젯을 통해 받은 결제 키")
|
|
||||||
val paymentKey: String,
|
|
||||||
|
|
||||||
@field:Schema(description = "결제 위젯을 통해 받은 주문번호.")
|
|
||||||
val orderId: String,
|
|
||||||
|
|
||||||
@field:Schema(description = "결제 위젯을 통해 받은 결제 금액")
|
|
||||||
val amount: Long,
|
|
||||||
|
|
||||||
@field:Schema(description = "결제 타입", example = "NORMAL")
|
|
||||||
val paymentType: String
|
|
||||||
) {
|
|
||||||
@get:JsonIgnore
|
|
||||||
val paymentRequest: PaymentApprove.Request
|
|
||||||
get() = PaymentApprove.Request(paymentKey, orderId, amount, paymentType)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Schema(name = "예약 대기 저장 요청", description = "회원의 예약 대기 요청시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class WaitingRequest(
|
|
||||||
@JvmField
|
|
||||||
@field:Schema(description = "예약 날짜. 지난 날짜는 지정할 수 없으며, yyyy-MM-dd 형식으로 입력해야 합니다.", type = "string", example = "2022-12-31")
|
|
||||||
val date: LocalDate,
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@field:Schema(description = "예약 시간 ID", example = "1")
|
|
||||||
val timeId: Long,
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@field:Schema(description = "테마 ID", example = "1")
|
|
||||||
val themeId: Long
|
|
||||||
)
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
package roomescape.reservation.web
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
|
||||||
import roomescape.member.web.MemberResponse
|
|
||||||
import roomescape.member.web.toResponse
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
|
||||||
import roomescape.theme.web.ThemeResponse
|
|
||||||
import roomescape.theme.web.toResponse
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
|
||||||
|
|
||||||
@Schema(name = "회원의 예약 및 대기 응답", description = "회원의 예약 및 대기 정보 응답시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class MyReservationResponse(
|
|
||||||
@field:Schema(description = "예약 번호. 예약을 식별할 때 사용합니다.")
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:Schema(description = "테마 이름")
|
|
||||||
val themeName: String,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약 날짜", type = "string", example = "2022-12-31")
|
|
||||||
val date: LocalDate,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약 시간", type = "string", example = "09:00")
|
|
||||||
val time: LocalTime,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약 상태", type = "string")
|
|
||||||
val status: ReservationStatus,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약 대기 상태일 때의 대기 순번. 확정된 예약은 0의 값을 가집니다.")
|
|
||||||
val rank: Long,
|
|
||||||
|
|
||||||
@field:Schema(description = "결제 키. 결제가 완료된 예약에만 값이 존재합니다.")
|
|
||||||
val paymentKey: String?,
|
|
||||||
|
|
||||||
@field:Schema(description = "결제 금액. 결제가 완료된 예약에만 값이 존재합니다.")
|
|
||||||
val amount: Long?
|
|
||||||
)
|
|
||||||
|
|
||||||
@Schema(name = "회원의 예약 및 대기 목록 조회 응답", description = "회원의 예약 및 대기 목록 조회 응답시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class MyReservationsResponse(
|
|
||||||
@field:Schema(description = "현재 로그인한 회원의 예약 및 대기 목록")
|
|
||||||
val reservations: List<MyReservationResponse>
|
|
||||||
)
|
|
||||||
|
|
||||||
@Schema(name = "예약 정보", description = "예약 저장 및 조회 응답에 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class ReservationResponse(
|
|
||||||
@JvmField
|
|
||||||
@field:Schema(description = "예약 번호. 예약을 식별할 때 사용합니다.")
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약 날짜", type = "string", example = "2022-12-31")
|
|
||||||
val date: LocalDate,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약한 회원 정보")
|
|
||||||
@field:JsonProperty("member")
|
|
||||||
val member: MemberResponse,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약 시간 정보")
|
|
||||||
@field:JsonProperty("time")
|
|
||||||
val time: ReservationTimeResponse,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약한 테마 정보")
|
|
||||||
@field:JsonProperty("theme")
|
|
||||||
val theme: ThemeResponse,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약 상태", type = "string")
|
|
||||||
val status: ReservationStatus
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun from(reservation: ReservationEntity): ReservationResponse {
|
|
||||||
return ReservationResponse(
|
|
||||||
reservation.id!!,
|
|
||||||
reservation.date,
|
|
||||||
reservation.member.toResponse(),
|
|
||||||
reservation.reservationTime.toResponse(),
|
|
||||||
reservation.theme.toResponse(),
|
|
||||||
reservation.reservationStatus
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ReservationEntity.toResponse(): ReservationResponse = ReservationResponse(
|
|
||||||
id = this.id!!,
|
|
||||||
date = this.date,
|
|
||||||
member = this.member.toResponse(),
|
|
||||||
time = this.reservationTime.toResponse(),
|
|
||||||
theme = this.theme.toResponse(),
|
|
||||||
status = this.reservationStatus
|
|
||||||
)
|
|
||||||
|
|
||||||
@Schema(name = "예약 목록 조회 응답", description = "모든 예약 정보 조회 응답시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class ReservationsResponse(
|
|
||||||
@field:Schema(description = "모든 예약 및 대기 목록")
|
|
||||||
val reservations: List<ReservationResponse>
|
|
||||||
)
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
package roomescape.reservation.web
|
|
||||||
|
|
||||||
import jakarta.validation.Valid
|
|
||||||
import org.springframework.http.ResponseEntity
|
|
||||||
import org.springframework.web.bind.annotation.*
|
|
||||||
import roomescape.common.dto.response.CommonApiResponse
|
|
||||||
import roomescape.reservation.business.ReservationTimeService
|
|
||||||
import roomescape.reservation.docs.ReservationTimeAPI
|
|
||||||
import java.net.URI
|
|
||||||
import java.time.LocalDate
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
class ReservationTimeController(
|
|
||||||
private val reservationTimeService: ReservationTimeService
|
|
||||||
) : ReservationTimeAPI {
|
|
||||||
|
|
||||||
@GetMapping("/times")
|
|
||||||
override fun getAllTimes(): ResponseEntity<CommonApiResponse<ReservationTimesResponse>> {
|
|
||||||
val response: ReservationTimesResponse = reservationTimeService.findAllTimes()
|
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/times")
|
|
||||||
override fun saveTime(
|
|
||||||
@Valid @RequestBody reservationTimeRequest: ReservationTimeRequest,
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationTimeResponse>> {
|
|
||||||
val response: ReservationTimeResponse = reservationTimeService.addTime(reservationTimeRequest)
|
|
||||||
|
|
||||||
return ResponseEntity
|
|
||||||
.created(URI.create("/times/${response.id}"))
|
|
||||||
.body(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/times/{id}")
|
|
||||||
override fun removeTime(@PathVariable id: Long): ResponseEntity<CommonApiResponse<Unit>> {
|
|
||||||
reservationTimeService.removeTimeById(id)
|
|
||||||
|
|
||||||
return ResponseEntity.noContent().build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/times/filter")
|
|
||||||
override fun findAllAvailableReservationTimes(
|
|
||||||
@RequestParam date: LocalDate,
|
|
||||||
@RequestParam themeId: Long
|
|
||||||
): ResponseEntity<CommonApiResponse<ReservationTimeInfosResponse>> {
|
|
||||||
val response: ReservationTimeInfosResponse = reservationTimeService.findAllAvailableTimesByDateAndTheme(date, themeId)
|
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
package roomescape.reservation.web
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
|
||||||
import java.time.LocalTime
|
|
||||||
|
|
||||||
@Schema(name = "예약 시간 저장 요청", description = "예약 시간 저장 요청시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class ReservationTimeRequest(
|
|
||||||
@JvmField
|
|
||||||
@field:Schema(description = "예약 시간. HH:mm 형식으로 입력해야 합니다.", type = "string", example = "09:00")
|
|
||||||
val startAt: LocalTime
|
|
||||||
)
|
|
||||||
|
|
||||||
@Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class ReservationTimeResponse(
|
|
||||||
@JvmField
|
|
||||||
@field:Schema(description = "예약 시간 번호. 예약 시간을 식별할 때 사용합니다.")
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약 시간", type = "string", example = "09:00")
|
|
||||||
val startAt: LocalTime
|
|
||||||
) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun from(reservationTime: ReservationTimeEntity): ReservationTimeResponse {
|
|
||||||
return ReservationTimeResponse(reservationTime.id!!, reservationTime.startAt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ReservationTimeEntity.toResponse(): ReservationTimeResponse = ReservationTimeResponse(
|
|
||||||
this.id!!, this.startAt
|
|
||||||
)
|
|
||||||
|
|
||||||
@Schema(name = "예약 시간 정보 목록 응답", description = "모든 예약 시간 조회 응답시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class ReservationTimesResponse(
|
|
||||||
@field:Schema(description = "모든 시간 목록")
|
|
||||||
val times: List<ReservationTimeResponse>
|
|
||||||
)
|
|
||||||
|
|
||||||
fun List<ReservationTimeEntity>.toResponses(): ReservationTimesResponse = ReservationTimesResponse(
|
|
||||||
this.map { it.toResponse() }
|
|
||||||
)
|
|
||||||
|
|
||||||
@Schema(name = "특정 테마, 날짜에 대한 시간 정보 응답", description = "특정 날짜와 테마에 대해, 예약 가능 여부를 포함한 시간 정보를 저장합니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class ReservationTimeInfoResponse(
|
|
||||||
@field:Schema(description = "예약 시간 번호. 예약 시간을 식별할 때 사용합니다.")
|
|
||||||
val id: Long,
|
|
||||||
|
|
||||||
@field:Schema(description = "예약 시간", type = "string", example = "09:00")
|
|
||||||
val startAt: LocalTime,
|
|
||||||
|
|
||||||
@field:Schema(description = "이미 예약이 완료된 시간인지 여부")
|
|
||||||
val alreadyBooked: Boolean
|
|
||||||
)
|
|
||||||
|
|
||||||
fun ReservationTimeEntity.toInfoResponse(alreadyBooked: Boolean): ReservationTimeInfoResponse = ReservationTimeInfoResponse(
|
|
||||||
id = this.id!!,
|
|
||||||
startAt = this.startAt,
|
|
||||||
alreadyBooked = alreadyBooked
|
|
||||||
)
|
|
||||||
|
|
||||||
@Schema(name = "예약 시간 정보 목록 응답", description = "특정 테마, 날짜에 대한 모든 예약 가능 시간 정보를 저장합니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class ReservationTimeInfosResponse(
|
|
||||||
@field:Schema(description = "특정 테마, 날짜에 대한 예약 가능 여부를 포함한 시간 목록")
|
|
||||||
val times: List<ReservationTimeInfoResponse>
|
|
||||||
)
|
|
||||||
@ -4,7 +4,7 @@ import org.springframework.stereotype.Service
|
|||||||
import roomescape.auth.infrastructure.jwt.JwtHandler
|
import roomescape.auth.infrastructure.jwt.JwtHandler
|
||||||
import roomescape.auth.web.LoginCheckResponse
|
import roomescape.auth.web.LoginCheckResponse
|
||||||
import roomescape.auth.web.LoginRequest
|
import roomescape.auth.web.LoginRequest
|
||||||
import roomescape.auth.web.TokenResponse
|
import roomescape.auth.web.LoginResponse
|
||||||
import roomescape.member.business.MemberService
|
import roomescape.member.business.MemberService
|
||||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||||
|
|
||||||
@ -13,15 +13,15 @@ class AuthService(
|
|||||||
private val memberService: MemberService,
|
private val memberService: MemberService,
|
||||||
private val jwtHandler: JwtHandler
|
private val jwtHandler: JwtHandler
|
||||||
) {
|
) {
|
||||||
fun login(request: LoginRequest): TokenResponse {
|
fun login(request: LoginRequest): LoginResponse {
|
||||||
val member: MemberEntity = memberService.findMemberByEmailAndPassword(
|
val member: MemberEntity = memberService.findByEmailAndPassword(
|
||||||
request.email,
|
request.email,
|
||||||
request.password
|
request.password
|
||||||
)
|
)
|
||||||
|
|
||||||
val accessToken: String = jwtHandler.createToken(member.id!!)
|
val accessToken: String = jwtHandler.createToken(member.id!!)
|
||||||
|
|
||||||
return TokenResponse(accessToken)
|
return LoginResponse(accessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkLogin(memberId: Long): LoginCheckResponse {
|
fun checkLogin(memberId: Long): LoginCheckResponse {
|
||||||
@ -24,7 +24,7 @@ class AuthController(
|
|||||||
override fun login(
|
override fun login(
|
||||||
@Valid @RequestBody loginRequest: LoginRequest,
|
@Valid @RequestBody loginRequest: LoginRequest,
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
val response: TokenResponse = authService.login(loginRequest)
|
val response: LoginResponse = authService.login(loginRequest)
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.header(HttpHeaders.SET_COOKIE, response.toResponseCookie())
|
.header(HttpHeaders.SET_COOKIE, response.toResponseCookie())
|
||||||
@ -4,27 +4,19 @@ import io.swagger.v3.oas.annotations.media.Schema
|
|||||||
import jakarta.validation.constraints.Email
|
import jakarta.validation.constraints.Email
|
||||||
import jakarta.validation.constraints.NotBlank
|
import jakarta.validation.constraints.NotBlank
|
||||||
|
|
||||||
@JvmRecord
|
data class LoginResponse(
|
||||||
data class TokenResponse(
|
|
||||||
val accessToken: String
|
val accessToken: String
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@Schema(name = "로그인 체크 응답", description = "로그인 상태 체크 응답시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class LoginCheckResponse(
|
data class LoginCheckResponse(
|
||||||
@field:Schema(description = "로그인된 회원의 이름")
|
@field:Schema(description = "로그인된 회원의 이름")
|
||||||
val name: String
|
val name: String
|
||||||
)
|
)
|
||||||
|
|
||||||
@Schema(name = "로그인 요청", description = "로그인 요청 시 사용됩니다.")
|
|
||||||
@JvmRecord
|
|
||||||
data class LoginRequest(
|
data class LoginRequest(
|
||||||
@Email(message = "이메일 형식이 일치하지 않습니다. 예시: abc123@gmail.com")
|
@Email(message = "이메일 형식이 일치하지 않습니다. 예시: abc123@gmail.com")
|
||||||
@field:Schema(description = "필수 값이며, 이메일 형식으로 입력해야 합니다.", example = "abc123@gmail.com")
|
|
||||||
val email: String,
|
val email: String,
|
||||||
|
|
||||||
@NotBlank(message = "비밀번호는 공백일 수 없습니다.")
|
@NotBlank(message = "비밀번호는 공백일 수 없습니다.")
|
||||||
@field:Schema(description = "최소 1글자 이상 입력해야 합니다.")
|
|
||||||
val password: String
|
val password: String
|
||||||
)
|
)
|
||||||
@ -3,7 +3,7 @@ package roomescape.auth.web.support
|
|||||||
import jakarta.servlet.http.Cookie
|
import jakarta.servlet.http.Cookie
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import org.springframework.http.ResponseCookie
|
import org.springframework.http.ResponseCookie
|
||||||
import roomescape.auth.web.TokenResponse
|
import roomescape.auth.web.LoginResponse
|
||||||
|
|
||||||
const val ACCESS_TOKEN_COOKIE_NAME = "accessToken"
|
const val ACCESS_TOKEN_COOKIE_NAME = "accessToken"
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ fun HttpServletRequest.accessTokenCookie(): Cookie = this.cookies
|
|||||||
?.firstOrNull { it.name == ACCESS_TOKEN_COOKIE_NAME }
|
?.firstOrNull { it.name == ACCESS_TOKEN_COOKIE_NAME }
|
||||||
?: Cookie(ACCESS_TOKEN_COOKIE_NAME, "")
|
?: Cookie(ACCESS_TOKEN_COOKIE_NAME, "")
|
||||||
|
|
||||||
fun TokenResponse.toResponseCookie(): String = accessTokenCookie(this.accessToken, 1800)
|
fun LoginResponse.toResponseCookie(): String = accessTokenCookie(this.accessToken, 1800)
|
||||||
.toString()
|
.toString()
|
||||||
|
|
||||||
fun expiredAccessTokenCookie(): String = accessTokenCookie("", 0)
|
fun expiredAccessTokenCookie(): String = accessTokenCookie("", 0)
|
||||||
@ -1,11 +1,7 @@
|
|||||||
package roomescape.common.exception
|
package roomescape.common.exception
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
|
||||||
import org.springframework.http.HttpStatus
|
|
||||||
|
|
||||||
enum class ErrorType(
|
enum class ErrorType(
|
||||||
@JvmField val description: String
|
val description: String
|
||||||
) {
|
) {
|
||||||
// 400 Bad Request
|
// 400 Bad Request
|
||||||
REQUEST_DATA_BLANK("요청 데이터에 유효하지 않은 값(null OR 공백)이 포함되어있습니다."),
|
REQUEST_DATA_BLANK("요청 데이터에 유효하지 않은 값(null OR 공백)이 포함되어있습니다."),
|
||||||
@ -30,7 +26,7 @@ enum class ErrorType(
|
|||||||
// 404 Not Found
|
// 404 Not Found
|
||||||
MEMBER_NOT_FOUND("회원(Member) 정보가 존재하지 않습니다."),
|
MEMBER_NOT_FOUND("회원(Member) 정보가 존재하지 않습니다."),
|
||||||
RESERVATION_NOT_FOUND("예약(Reservation) 정보가 존재하지 않습니다."),
|
RESERVATION_NOT_FOUND("예약(Reservation) 정보가 존재하지 않습니다."),
|
||||||
RESERVATION_TIME_NOT_FOUND("예약 시간(ReservationTime) 정보가 존재하지 않습니다."),
|
TIME_NOT_FOUND("예약 시간(Time) 정보가 존재하지 않습니다."),
|
||||||
THEME_NOT_FOUND("테마(Theme) 정보가 존재하지 않습니다."),
|
THEME_NOT_FOUND("테마(Theme) 정보가 존재하지 않습니다."),
|
||||||
PAYMENT_NOT_FOUND("결제(Payment) 정보가 존재하지 않습니다."),
|
PAYMENT_NOT_FOUND("결제(Payment) 정보가 존재하지 않습니다."),
|
||||||
|
|
||||||
@ -54,18 +50,4 @@ enum class ErrorType(
|
|||||||
PAYMENT_ERROR("결제(취소)에 실패했습니다. 결제(취소) 정보를 확인해주세요."),
|
PAYMENT_ERROR("결제(취소)에 실패했습니다. 결제(취소) 정보를 확인해주세요."),
|
||||||
PAYMENT_SERVER_ERROR("결제 서버에서 에러가 발생하였습니다. 잠시 후 다시 시도해주세요.")
|
PAYMENT_SERVER_ERROR("결제 서버에서 에러가 발생하였습니다. 잠시 후 다시 시도해주세요.")
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
@JsonCreator
|
|
||||||
fun from(@JsonProperty("errorType") errorType: String): ErrorType {
|
|
||||||
return entries.toTypedArray()
|
|
||||||
.firstOrNull { it.name == errorType }
|
|
||||||
?: throw RoomescapeException(
|
|
||||||
INVALID_REQUEST_DATA,
|
|
||||||
"[ErrorType: ${errorType}]",
|
|
||||||
HttpStatus.BAD_REQUEST
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -5,11 +5,9 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
|||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException
|
import org.springframework.http.converter.HttpMessageNotReadableException
|
||||||
import org.springframework.web.HttpRequestMethodNotSupportedException
|
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||||
import org.springframework.web.client.ResourceAccessException
|
|
||||||
import roomescape.common.dto.response.CommonErrorResponse
|
import roomescape.common.dto.response.CommonErrorResponse
|
||||||
|
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
@ -26,15 +24,6 @@ class ExceptionControllerAdvice(
|
|||||||
.body(CommonErrorResponse(e.errorType))
|
.body(CommonErrorResponse(e.errorType))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(ResourceAccessException::class)
|
|
||||||
fun handleResourceAccessException(e: ResourceAccessException): ResponseEntity<CommonErrorResponse> {
|
|
||||||
logger.error(e) { "message: ${e.message}" }
|
|
||||||
|
|
||||||
return ResponseEntity
|
|
||||||
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
||||||
.body(CommonErrorResponse(ErrorType.PAYMENT_SERVER_ERROR))
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(value = [HttpMessageNotReadableException::class])
|
@ExceptionHandler(value = [HttpMessageNotReadableException::class])
|
||||||
fun handleHttpMessageNotReadableException(e: HttpMessageNotReadableException): ResponseEntity<CommonErrorResponse> {
|
fun handleHttpMessageNotReadableException(e: HttpMessageNotReadableException): ResponseEntity<CommonErrorResponse> {
|
||||||
logger.error(e) { "message: ${e.message}" }
|
logger.error(e) { "message: ${e.message}" }
|
||||||
@ -56,15 +45,6 @@ class ExceptionControllerAdvice(
|
|||||||
.body(CommonErrorResponse(ErrorType.INVALID_REQUEST_DATA, messages))
|
.body(CommonErrorResponse(ErrorType.INVALID_REQUEST_DATA, messages))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(value = [HttpRequestMethodNotSupportedException::class])
|
|
||||||
fun handleHttpRequestMethodNotSupportedException(e: HttpRequestMethodNotSupportedException): ResponseEntity<CommonErrorResponse> {
|
|
||||||
logger.error(e) { "message: ${e.message}" }
|
|
||||||
|
|
||||||
return ResponseEntity
|
|
||||||
.status(HttpStatus.METHOD_NOT_ALLOWED)
|
|
||||||
.body(CommonErrorResponse(ErrorType.METHOD_NOT_ALLOWED))
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(value = [Exception::class])
|
@ExceptionHandler(value = [Exception::class])
|
||||||
fun handleException(e: Exception): ResponseEntity<CommonErrorResponse> {
|
fun handleException(e: Exception): ResponseEntity<CommonErrorResponse> {
|
||||||
logger.error(e) { "message: ${e.message}" }
|
logger.error(e) { "message: ${e.message}" }
|
||||||
@ -8,17 +8,17 @@ import roomescape.common.exception.ErrorType
|
|||||||
import roomescape.common.exception.RoomescapeException
|
import roomescape.common.exception.RoomescapeException
|
||||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||||
import roomescape.member.web.MembersResponse
|
import roomescape.member.web.MemberRetrieveListResponse
|
||||||
import roomescape.member.web.toResponse
|
import roomescape.member.web.toRetrieveResponse
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
class MemberService(
|
class MemberService(
|
||||||
private val memberRepository: MemberRepository
|
private val memberRepository: MemberRepository
|
||||||
) {
|
) {
|
||||||
fun readAllMembers(): MembersResponse = MembersResponse(
|
fun findMembers(): MemberRetrieveListResponse = MemberRetrieveListResponse(
|
||||||
memberRepository.findAll()
|
memberRepository.findAll()
|
||||||
.map { it.toResponse() }
|
.map { it.toRetrieveResponse() }
|
||||||
.toList()
|
.toList()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ class MemberService(
|
|||||||
HttpStatus.BAD_REQUEST
|
HttpStatus.BAD_REQUEST
|
||||||
)
|
)
|
||||||
|
|
||||||
fun findMemberByEmailAndPassword(email: String, password: String): MemberEntity =
|
fun findByEmailAndPassword(email: String, password: String): MemberEntity =
|
||||||
memberRepository.findByEmailAndPassword(email, password)
|
memberRepository.findByEmailAndPassword(email, password)
|
||||||
?: throw RoomescapeException(
|
?: throw RoomescapeException(
|
||||||
ErrorType.MEMBER_NOT_FOUND,
|
ErrorType.MEMBER_NOT_FOUND,
|
||||||
@ -7,7 +7,7 @@ import io.swagger.v3.oas.annotations.tags.Tag
|
|||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import roomescape.auth.web.support.Admin
|
import roomescape.auth.web.support.Admin
|
||||||
import roomescape.common.dto.response.CommonApiResponse
|
import roomescape.common.dto.response.CommonApiResponse
|
||||||
import roomescape.member.web.MembersResponse
|
import roomescape.member.web.MemberRetrieveListResponse
|
||||||
|
|
||||||
@Tag(name = "2. 회원 API", description = "회원 정보를 관리할 때 사용합니다.")
|
@Tag(name = "2. 회원 API", description = "회원 정보를 관리할 때 사용합니다.")
|
||||||
interface MemberAPI {
|
interface MemberAPI {
|
||||||
@ -20,5 +20,5 @@ interface MemberAPI {
|
|||||||
useReturnTypeSchema = true
|
useReturnTypeSchema = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fun readAllMembers(): ResponseEntity<CommonApiResponse<MembersResponse>>
|
fun findMembers(): ResponseEntity<CommonApiResponse<MemberRetrieveListResponse>>
|
||||||
}
|
}
|
||||||
@ -3,7 +3,7 @@ package roomescape.member.infrastructure.persistence
|
|||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "member")
|
@Table(name = "members")
|
||||||
class MemberEntity(
|
class MemberEntity(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@ -13,8 +13,8 @@ class MemberController(
|
|||||||
) : MemberAPI {
|
) : MemberAPI {
|
||||||
|
|
||||||
@GetMapping("/members")
|
@GetMapping("/members")
|
||||||
override fun readAllMembers(): ResponseEntity<CommonApiResponse<MembersResponse>> {
|
override fun findMembers(): ResponseEntity<CommonApiResponse<MemberRetrieveListResponse>> {
|
||||||
val response: MembersResponse = memberService.readAllMembers()
|
val response: MemberRetrieveListResponse = memberService.findMembers()
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
21
src/main/kotlin/roomescape/member/web/MemberDTO.kt
Normal file
21
src/main/kotlin/roomescape/member/web/MemberDTO.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package roomescape.member.web
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||||
|
|
||||||
|
fun MemberEntity.toRetrieveResponse(): MemberRetrieveResponse = MemberRetrieveResponse(
|
||||||
|
id = id!!,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MemberRetrieveResponse(
|
||||||
|
@field:Schema(description = "회원 식별자")
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@field:Schema(description = "회원 이름")
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MemberRetrieveListResponse(
|
||||||
|
val members: List<MemberRetrieveResponse>
|
||||||
|
)
|
||||||
@ -5,14 +5,15 @@ import org.springframework.stereotype.Service
|
|||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import roomescape.common.exception.ErrorType
|
import roomescape.common.exception.ErrorType
|
||||||
import roomescape.common.exception.RoomescapeException
|
import roomescape.common.exception.RoomescapeException
|
||||||
|
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
||||||
import roomescape.payment.web.PaymentApprove
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
import roomescape.payment.web.PaymentCancel
|
import roomescape.payment.web.PaymentCancelResponse
|
||||||
import roomescape.payment.web.ReservationPaymentResponse
|
import roomescape.payment.web.PaymentCreateResponse
|
||||||
import roomescape.payment.web.toReservationPaymentResponse
|
import roomescape.payment.web.toCreateResponse
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
@ -22,10 +23,10 @@ class PaymentService(
|
|||||||
private val canceledPaymentRepository: CanceledPaymentRepository
|
private val canceledPaymentRepository: CanceledPaymentRepository
|
||||||
) {
|
) {
|
||||||
@Transactional
|
@Transactional
|
||||||
fun savePayment(
|
fun createPayment(
|
||||||
paymentResponse: PaymentApprove.Response,
|
paymentResponse: PaymentApproveResponse,
|
||||||
reservation: ReservationEntity
|
reservation: ReservationEntity
|
||||||
): ReservationPaymentResponse = PaymentEntity(
|
): PaymentCreateResponse = PaymentEntity(
|
||||||
orderId = paymentResponse.orderId,
|
orderId = paymentResponse.orderId,
|
||||||
paymentKey = paymentResponse.paymentKey,
|
paymentKey = paymentResponse.paymentKey,
|
||||||
totalAmount = paymentResponse.totalAmount,
|
totalAmount = paymentResponse.totalAmount,
|
||||||
@ -33,7 +34,7 @@ class PaymentService(
|
|||||||
approvedAt = paymentResponse.approvedAt
|
approvedAt = paymentResponse.approvedAt
|
||||||
).also {
|
).also {
|
||||||
paymentRepository.save(it)
|
paymentRepository.save(it)
|
||||||
}.toReservationPaymentResponse()
|
}.toCreateResponse()
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun isReservationPaid(
|
fun isReservationPaid(
|
||||||
@ -41,8 +42,8 @@ class PaymentService(
|
|||||||
): Boolean = paymentRepository.existsByReservationId(reservationId)
|
): Boolean = paymentRepository.existsByReservationId(reservationId)
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun saveCanceledPayment(
|
fun createCanceledPayment(
|
||||||
cancelInfo: PaymentCancel.Response,
|
cancelInfo: PaymentCancelResponse,
|
||||||
approvedAt: OffsetDateTime,
|
approvedAt: OffsetDateTime,
|
||||||
paymentKey: String
|
paymentKey: String
|
||||||
): CanceledPaymentEntity = CanceledPaymentEntity(
|
): CanceledPaymentEntity = CanceledPaymentEntity(
|
||||||
@ -53,9 +54,8 @@ class PaymentService(
|
|||||||
canceledAt = cancelInfo.canceledAt
|
canceledAt = cancelInfo.canceledAt
|
||||||
).also { canceledPaymentRepository.save(it) }
|
).also { canceledPaymentRepository.save(it) }
|
||||||
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun cancelPaymentByAdmin(reservationId: Long): PaymentCancel.Request {
|
fun createCanceledPaymentByReservationId(reservationId: Long): PaymentCancelRequest {
|
||||||
val paymentKey: String = paymentRepository.findPaymentKeyByReservationId(reservationId)
|
val paymentKey: String = paymentRepository.findPaymentKeyByReservationId(reservationId)
|
||||||
?: throw RoomescapeException(
|
?: throw RoomescapeException(
|
||||||
ErrorType.PAYMENT_NOT_FOUND,
|
ErrorType.PAYMENT_NOT_FOUND,
|
||||||
@ -65,7 +65,7 @@ class PaymentService(
|
|||||||
// 취소 시간은 현재 시간으로 일단 생성한 뒤, 결제 취소 완료 후 해당 시간으로 변경합니다.
|
// 취소 시간은 현재 시간으로 일단 생성한 뒤, 결제 취소 완료 후 해당 시간으로 변경합니다.
|
||||||
val canceled: CanceledPaymentEntity = cancelPayment(paymentKey)
|
val canceled: CanceledPaymentEntity = cancelPayment(paymentKey)
|
||||||
|
|
||||||
return PaymentCancel.Request(paymentKey, canceled.cancelAmount, canceled.cancelReason)
|
return PaymentCancelRequest(paymentKey, canceled.cancelAmount, canceled.cancelReason)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelPayment(
|
private fun cancelPayment(
|
||||||
@ -5,23 +5,23 @@ import com.fasterxml.jackson.core.TreeNode
|
|||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||||
import roomescape.payment.web.PaymentCancel
|
import roomescape.payment.web.PaymentCancelResponse
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
class PaymentCancelResponseDeserializer(
|
class PaymentCancelResponseDeserializer(
|
||||||
vc: Class<PaymentCancel.Response>? = null
|
vc: Class<PaymentCancelResponse>? = null
|
||||||
) : StdDeserializer<PaymentCancel.Response>(vc) {
|
) : StdDeserializer<PaymentCancelResponse>(vc) {
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun deserialize(
|
override fun deserialize(
|
||||||
jsonParser: JsonParser,
|
jsonParser: JsonParser,
|
||||||
deserializationContext: DeserializationContext?
|
deserializationContext: DeserializationContext?
|
||||||
): PaymentCancel.Response {
|
): PaymentCancelResponse {
|
||||||
val cancels: JsonNode = jsonParser.codec.readTree<TreeNode>(jsonParser)
|
val cancels: JsonNode = jsonParser.codec.readTree<TreeNode>(jsonParser)
|
||||||
.get("cancels")
|
.get("cancels")
|
||||||
.get(0) as JsonNode
|
.get(0) as JsonNode
|
||||||
|
|
||||||
return PaymentCancel.Response(
|
return PaymentCancelResponse(
|
||||||
cancels.get("cancelStatus").asText(),
|
cancels.get("cancelStatus").asText(),
|
||||||
cancels.get("cancelReason").asText(),
|
cancels.get("cancelReason").asText(),
|
||||||
cancels.get("cancelAmount").asLong(),
|
cancels.get("cancelAmount").asLong(),
|
||||||
@ -4,8 +4,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties
|
|||||||
|
|
||||||
@ConfigurationProperties(prefix = "payment")
|
@ConfigurationProperties(prefix = "payment")
|
||||||
data class PaymentProperties(
|
data class PaymentProperties(
|
||||||
@JvmField val apiBaseUrl: String,
|
val apiBaseUrl: String,
|
||||||
@JvmField val confirmSecretKey: String,
|
val confirmSecretKey: String,
|
||||||
@JvmField val readTimeout: Int,
|
val readTimeout: Int,
|
||||||
@JvmField val connectTimeout: Int
|
val connectTimeout: Int
|
||||||
)
|
)
|
||||||
@ -12,15 +12,16 @@ import org.springframework.stereotype.Component
|
|||||||
import org.springframework.web.client.RestClient
|
import org.springframework.web.client.RestClient
|
||||||
import roomescape.common.exception.ErrorType
|
import roomescape.common.exception.ErrorType
|
||||||
import roomescape.common.exception.RoomescapeException
|
import roomescape.common.exception.RoomescapeException
|
||||||
import roomescape.payment.web.PaymentApprove
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
import roomescape.payment.web.PaymentCancel
|
import roomescape.payment.web.PaymentCancelResponse
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.Map
|
import java.util.Map
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class TossPaymentClient(
|
class TossPaymentClient(
|
||||||
private val log: KLogger = KotlinLogging.logger {},
|
private val log: KLogger = KotlinLogging.logger {},
|
||||||
tossPaymentClientBuilder: RestClient.Builder
|
private val objectMapper: ObjectMapper,
|
||||||
|
tossPaymentClientBuilder: RestClient.Builder,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val CONFIRM_URL: String = "/v1/payments/confirm"
|
private const val CONFIRM_URL: String = "/v1/payments/confirm"
|
||||||
@ -29,7 +30,7 @@ class TossPaymentClient(
|
|||||||
|
|
||||||
private val tossPaymentClient: RestClient = tossPaymentClientBuilder.build()
|
private val tossPaymentClient: RestClient = tossPaymentClientBuilder.build()
|
||||||
|
|
||||||
fun confirmPayment(paymentRequest: PaymentApprove.Request): PaymentApprove.Response {
|
fun confirm(paymentRequest: PaymentApproveRequest): PaymentApproveResponse {
|
||||||
logPaymentInfo(paymentRequest)
|
logPaymentInfo(paymentRequest)
|
||||||
|
|
||||||
return tossPaymentClient.post()
|
return tossPaymentClient.post()
|
||||||
@ -41,11 +42,11 @@ class TossPaymentClient(
|
|||||||
{ status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError },
|
{ status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError },
|
||||||
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) }
|
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) }
|
||||||
)
|
)
|
||||||
.body(PaymentApprove.Response::class.java)
|
.body(PaymentApproveResponse::class.java)
|
||||||
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR)
|
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelPayment(cancelRequest: PaymentCancel.Request): PaymentCancel.Response {
|
fun cancel(cancelRequest: PaymentCancelRequest): PaymentCancelResponse {
|
||||||
logPaymentCancelInfo(cancelRequest)
|
logPaymentCancelInfo(cancelRequest)
|
||||||
val param = Map.of<String, String>("cancelReason", cancelRequest.cancelReason)
|
val param = Map.of<String, String>("cancelReason", cancelRequest.cancelReason)
|
||||||
|
|
||||||
@ -58,18 +59,18 @@ class TossPaymentClient(
|
|||||||
{ status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError },
|
{ status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError },
|
||||||
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) }
|
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) }
|
||||||
)
|
)
|
||||||
.body(PaymentCancel.Response::class.java)
|
.body(PaymentCancelResponse::class.java)
|
||||||
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR)
|
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logPaymentInfo(paymentRequest: PaymentApprove.Request) {
|
private fun logPaymentInfo(paymentRequest: PaymentApproveRequest) {
|
||||||
log.info {
|
log.info {
|
||||||
"결제 승인 요청: paymentKey=${paymentRequest.paymentKey}, orderId=${paymentRequest.orderId}, " +
|
"결제 승인 요청: paymentKey=${paymentRequest.paymentKey}, orderId=${paymentRequest.orderId}, " +
|
||||||
"amount=${paymentRequest.amount}, paymentType=${paymentRequest.paymentType}"
|
"amount=${paymentRequest.amount}, paymentType=${paymentRequest.paymentType}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logPaymentCancelInfo(cancelRequest: PaymentCancel.Request) {
|
private fun logPaymentCancelInfo(cancelRequest: PaymentCancelRequest) {
|
||||||
log.info {
|
log.info {
|
||||||
"결제 취소 요청: paymentKey=${cancelRequest.paymentKey}, amount=${cancelRequest.amount}, " +
|
"결제 취소 요청: paymentKey=${cancelRequest.paymentKey}, amount=${cancelRequest.amount}, " +
|
||||||
"cancelReason=${cancelRequest.cancelReason}"
|
"cancelReason=${cancelRequest.cancelReason}"
|
||||||
@ -96,7 +97,6 @@ class TossPaymentClient(
|
|||||||
res: ClientHttpResponse
|
res: ClientHttpResponse
|
||||||
): TossPaymentErrorResponse {
|
): TossPaymentErrorResponse {
|
||||||
val body = res.body
|
val body = res.body
|
||||||
val objectMapper = ObjectMapper()
|
|
||||||
val errorResponse = objectMapper.readValue(body, TossPaymentErrorResponse::class.java)
|
val errorResponse = objectMapper.readValue(body, TossPaymentErrorResponse::class.java)
|
||||||
body.close()
|
body.close()
|
||||||
return errorResponse
|
return errorResponse
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package roomescape.payment.infrastructure.client
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
data class TossPaymentErrorResponse(
|
||||||
|
val code: String,
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PaymentApproveRequest(
|
||||||
|
val paymentKey: String,
|
||||||
|
val orderId: String,
|
||||||
|
val amount: Long,
|
||||||
|
val paymentType: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class PaymentApproveResponse(
|
||||||
|
val paymentKey: String,
|
||||||
|
val orderId: String,
|
||||||
|
val totalAmount: Long,
|
||||||
|
val approvedAt: OffsetDateTime
|
||||||
|
)
|
||||||
@ -4,7 +4,7 @@ import jakarta.persistence.*
|
|||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "canceled_payment")
|
@Table(name = "canceled_payments")
|
||||||
class CanceledPaymentEntity(
|
class CanceledPaymentEntity(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@ -5,7 +5,7 @@ import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
|||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "payment")
|
@Table(name = "payments")
|
||||||
class PaymentEntity(
|
class PaymentEntity(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
40
src/main/kotlin/roomescape/payment/web/PaymentDTO.kt
Normal file
40
src/main/kotlin/roomescape/payment/web/PaymentDTO.kt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package roomescape.payment.web
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer
|
||||||
|
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||||
|
import roomescape.reservation.web.ReservationRetrieveResponse
|
||||||
|
import roomescape.reservation.web.toRetrieveResponse
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
|
data class PaymentCancelRequest(
|
||||||
|
val paymentKey: String,
|
||||||
|
val amount: Long,
|
||||||
|
val cancelReason: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonDeserialize(using = PaymentCancelResponseDeserializer::class)
|
||||||
|
data class PaymentCancelResponse(
|
||||||
|
val cancelStatus: String,
|
||||||
|
val cancelReason: String,
|
||||||
|
val cancelAmount: Long,
|
||||||
|
val canceledAt: OffsetDateTime
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PaymentCreateResponse(
|
||||||
|
val id: Long,
|
||||||
|
val orderId: String,
|
||||||
|
val paymentKey: String,
|
||||||
|
val totalAmount: Long,
|
||||||
|
val reservation: ReservationRetrieveResponse,
|
||||||
|
val approvedAt: OffsetDateTime
|
||||||
|
)
|
||||||
|
|
||||||
|
fun PaymentEntity.toCreateResponse(): PaymentCreateResponse = PaymentCreateResponse(
|
||||||
|
id = this.id!!,
|
||||||
|
orderId = this.orderId,
|
||||||
|
paymentKey = this.paymentKey,
|
||||||
|
totalAmount = this.totalAmount,
|
||||||
|
reservation = this.reservation.toRetrieveResponse(),
|
||||||
|
approvedAt = this.approvedAt
|
||||||
|
)
|
||||||
@ -18,40 +18,40 @@ import java.time.LocalDateTime
|
|||||||
@Transactional
|
@Transactional
|
||||||
class ReservationService(
|
class ReservationService(
|
||||||
private val reservationRepository: ReservationRepository,
|
private val reservationRepository: ReservationRepository,
|
||||||
private val reservationTimeService: ReservationTimeService,
|
private val timeService: TimeService,
|
||||||
private val memberService: MemberService,
|
private val memberService: MemberService,
|
||||||
private val themeService: ThemeService,
|
private val themeService: ThemeService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findAllReservations(): ReservationsResponse {
|
fun findReservations(): ReservationRetrieveListResponse {
|
||||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||||
.confirmed()
|
.confirmed()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
||||||
return ReservationsResponse(findAllReservationByStatus(spec))
|
return ReservationRetrieveListResponse(findAllReservationByStatus(spec))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findAllWaiting(): ReservationsResponse {
|
fun findAllWaiting(): ReservationRetrieveListResponse {
|
||||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||||
.waiting()
|
.waiting()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return ReservationsResponse(findAllReservationByStatus(spec))
|
return ReservationRetrieveListResponse(findAllReservationByStatus(spec))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findAllReservationByStatus(spec: Specification<ReservationEntity>): List<ReservationResponse> {
|
private fun findAllReservationByStatus(spec: Specification<ReservationEntity>): List<ReservationRetrieveResponse> {
|
||||||
return reservationRepository.findAll(spec).map { it.toResponse() }
|
return reservationRepository.findAll(spec).map { it.toRetrieveResponse() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeReservationById(reservationId: Long, memberId: Long) {
|
fun deleteReservation(reservationId: Long, memberId: Long) {
|
||||||
validateIsMemberAdmin(memberId)
|
validateIsMemberAdmin(memberId)
|
||||||
reservationRepository.deleteById(reservationId)
|
reservationRepository.deleteById(reservationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addReservation(request: ReservationRequest, memberId: Long): ReservationEntity {
|
fun addReservation(request: ReservationCreateWithPaymentRequest, memberId: Long): ReservationEntity {
|
||||||
validateIsReservationExist(request.themeId, request.timeId, request.date)
|
validateIsReservationExist(request.themeId, request.timeId, request.date)
|
||||||
return getReservationForSave(
|
return getReservationForSave(
|
||||||
request.timeId,
|
request.timeId,
|
||||||
@ -64,7 +64,7 @@ class ReservationService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addReservationByAdmin(request: AdminReservationRequest): ReservationResponse {
|
fun createReservationByAdmin(request: AdminReservationCreateRequest): ReservationRetrieveResponse {
|
||||||
validateIsReservationExist(request.themeId, request.timeId, request.date)
|
validateIsReservationExist(request.themeId, request.timeId, request.date)
|
||||||
|
|
||||||
return addReservationWithoutPayment(
|
return addReservationWithoutPayment(
|
||||||
@ -76,7 +76,7 @@ class ReservationService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addWaiting(request: WaitingRequest, memberId: Long): ReservationResponse {
|
fun createWaiting(request: WaitingCreateRequest, memberId: Long): ReservationRetrieveResponse {
|
||||||
validateMemberAlreadyReserve(request.themeId, request.timeId, request.date, memberId)
|
validateMemberAlreadyReserve(request.themeId, request.timeId, request.date, memberId)
|
||||||
return addReservationWithoutPayment(
|
return addReservationWithoutPayment(
|
||||||
request.themeId,
|
request.themeId,
|
||||||
@ -93,11 +93,10 @@ class ReservationService(
|
|||||||
date: LocalDate,
|
date: LocalDate,
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
status: ReservationStatus
|
status: ReservationStatus
|
||||||
): ReservationResponse = getReservationForSave(timeId, themeId, date, memberId, status)
|
): ReservationRetrieveResponse = getReservationForSave(timeId, themeId, date, memberId, status)
|
||||||
.also {
|
.also {
|
||||||
reservationRepository.save(it)
|
reservationRepository.save(it)
|
||||||
}.toResponse()
|
}.toRetrieveResponse()
|
||||||
|
|
||||||
|
|
||||||
private fun validateMemberAlreadyReserve(themeId: Long?, timeId: Long?, date: LocalDate?, memberId: Long?) {
|
private fun validateMemberAlreadyReserve(themeId: Long?, timeId: Long?, date: LocalDate?, memberId: Long?) {
|
||||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||||
@ -127,10 +126,10 @@ class ReservationService(
|
|||||||
|
|
||||||
private fun validateDateAndTime(
|
private fun validateDateAndTime(
|
||||||
requestDate: LocalDate,
|
requestDate: LocalDate,
|
||||||
requestReservationTime: ReservationTimeEntity
|
requestTime: TimeEntity
|
||||||
) {
|
) {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val request = LocalDateTime.of(requestDate, requestReservationTime.startAt)
|
val request = LocalDateTime.of(requestDate, requestTime.startAt)
|
||||||
|
|
||||||
if (request.isBefore(now)) {
|
if (request.isBefore(now)) {
|
||||||
throw RoomescapeException(
|
throw RoomescapeException(
|
||||||
@ -148,15 +147,15 @@ class ReservationService(
|
|||||||
memberId: Long,
|
memberId: Long,
|
||||||
status: ReservationStatus
|
status: ReservationStatus
|
||||||
): ReservationEntity {
|
): ReservationEntity {
|
||||||
val time = reservationTimeService.findTimeById(timeId)
|
val time = timeService.findById(timeId)
|
||||||
val theme = themeService.findThemeById(themeId)
|
val theme = themeService.findById(themeId)
|
||||||
val member = memberService.findById(memberId)
|
val member = memberService.findById(memberId)
|
||||||
|
|
||||||
validateDateAndTime(date, time)
|
validateDateAndTime(date, time)
|
||||||
|
|
||||||
return ReservationEntity(
|
return ReservationEntity(
|
||||||
date = date,
|
date = date,
|
||||||
reservationTime = time,
|
time = time,
|
||||||
theme = theme,
|
theme = theme,
|
||||||
member = member,
|
member = member,
|
||||||
reservationStatus = status
|
reservationStatus = status
|
||||||
@ -164,12 +163,12 @@ class ReservationService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findFilteredReservations(
|
fun searchReservations(
|
||||||
themeId: Long?,
|
themeId: Long?,
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
dateFrom: LocalDate?,
|
dateFrom: LocalDate?,
|
||||||
dateTo: LocalDate?
|
dateTo: LocalDate?
|
||||||
): ReservationsResponse {
|
): ReservationRetrieveListResponse {
|
||||||
validateDateForSearch(dateFrom, dateTo)
|
validateDateForSearch(dateFrom, dateTo)
|
||||||
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
val spec: Specification<ReservationEntity> = ReservationSearchSpecification()
|
||||||
.confirmed()
|
.confirmed()
|
||||||
@ -179,7 +178,7 @@ class ReservationService(
|
|||||||
.dateEndAt(dateTo)
|
.dateEndAt(dateTo)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return ReservationsResponse(findAllReservationByStatus(spec))
|
return ReservationRetrieveListResponse(findAllReservationByStatus(spec))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateDateForSearch(startFrom: LocalDate?, endAt: LocalDate?) {
|
private fun validateDateForSearch(startFrom: LocalDate?, endAt: LocalDate?) {
|
||||||
@ -195,11 +194,11 @@ class ReservationService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findMemberReservations(memberId: Long): MyReservationsResponse {
|
fun findReservationsByMemberId(memberId: Long): MyReservationRetrieveListResponse {
|
||||||
return MyReservationsResponse(reservationRepository.findMyReservations(memberId))
|
return MyReservationRetrieveListResponse(reservationRepository.findAllById(memberId))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun approveWaiting(reservationId: Long, memberId: Long) {
|
fun confirmWaiting(reservationId: Long, memberId: Long) {
|
||||||
validateIsMemberAdmin(memberId)
|
validateIsMemberAdmin(memberId)
|
||||||
if (reservationRepository.isExistConfirmedReservation(reservationId)) {
|
if (reservationRepository.isExistConfirmedReservation(reservationId)) {
|
||||||
throw RoomescapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT)
|
throw RoomescapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT)
|
||||||
@ -207,7 +206,7 @@ class ReservationService(
|
|||||||
reservationRepository.updateStatusByReservationId(reservationId, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)
|
reservationRepository.updateStatusByReservationId(reservationId, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelWaiting(reservationId: Long, memberId: Long) {
|
fun deleteWaiting(reservationId: Long, memberId: Long) {
|
||||||
reservationRepository.findByIdOrNull(reservationId)?.takeIf {
|
reservationRepository.findByIdOrNull(reservationId)?.takeIf {
|
||||||
it.isWaiting() && it.isSameMember(memberId)
|
it.isWaiting() && it.isSameMember(memberId)
|
||||||
}?.let {
|
}?.let {
|
||||||
@ -215,7 +214,7 @@ class ReservationService(
|
|||||||
} ?: throw throwReservationNotFound(reservationId)
|
} ?: throw throwReservationNotFound(reservationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun denyWaiting(reservationId: Long, memberId: Long) {
|
fun rejectWaiting(reservationId: Long, memberId: Long) {
|
||||||
validateIsMemberAdmin(memberId)
|
validateIsMemberAdmin(memberId)
|
||||||
reservationRepository.findByIdOrNull(reservationId)?.takeIf {
|
reservationRepository.findByIdOrNull(reservationId)?.takeIf {
|
||||||
it.isWaiting()
|
it.isWaiting()
|
||||||
@ -3,11 +3,12 @@ package roomescape.reservation.business
|
|||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import roomescape.payment.business.PaymentService
|
import roomescape.payment.business.PaymentService
|
||||||
import roomescape.payment.web.PaymentApprove
|
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
||||||
import roomescape.payment.web.PaymentCancel
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
|
import roomescape.payment.web.PaymentCancelResponse
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||||
import roomescape.reservation.web.ReservationRequest
|
import roomescape.reservation.web.ReservationCreateWithPaymentRequest
|
||||||
import roomescape.reservation.web.ReservationResponse
|
import roomescape.reservation.web.ReservationRetrieveResponse
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -16,31 +17,31 @@ class ReservationWithPaymentService(
|
|||||||
private val reservationService: ReservationService,
|
private val reservationService: ReservationService,
|
||||||
private val paymentService: PaymentService
|
private val paymentService: PaymentService
|
||||||
) {
|
) {
|
||||||
fun addReservationWithPayment(
|
fun createReservationAndPayment(
|
||||||
request: ReservationRequest,
|
request: ReservationCreateWithPaymentRequest,
|
||||||
paymentInfo: PaymentApprove.Response,
|
paymentInfo: PaymentApproveResponse,
|
||||||
memberId: Long
|
memberId: Long
|
||||||
): ReservationResponse {
|
): ReservationRetrieveResponse {
|
||||||
val reservation: ReservationEntity = reservationService.addReservation(request, memberId)
|
val reservation: ReservationEntity = reservationService.addReservation(request, memberId)
|
||||||
|
|
||||||
return paymentService.savePayment(paymentInfo, reservation)
|
return paymentService.createPayment(paymentInfo, reservation)
|
||||||
.reservation
|
.reservation
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveCanceledPayment(
|
fun createCanceledPayment(
|
||||||
cancelInfo: PaymentCancel.Response,
|
cancelInfo: PaymentCancelResponse,
|
||||||
approvedAt: OffsetDateTime,
|
approvedAt: OffsetDateTime,
|
||||||
paymentKey: String
|
paymentKey: String
|
||||||
) {
|
) {
|
||||||
paymentService.saveCanceledPayment(cancelInfo, approvedAt, paymentKey)
|
paymentService.createCanceledPayment(cancelInfo, approvedAt, paymentKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeReservationWithPayment(
|
fun deleteReservationAndPayment(
|
||||||
reservationId: Long,
|
reservationId: Long,
|
||||||
memberId: Long
|
memberId: Long
|
||||||
): PaymentCancel.Request {
|
): PaymentCancelRequest {
|
||||||
val paymentCancelRequest = paymentService.cancelPaymentByAdmin(reservationId)
|
val paymentCancelRequest = paymentService.createCanceledPaymentByReservationId(reservationId)
|
||||||
reservationService.removeReservationById(reservationId, memberId)
|
reservationService.deleteReservation(reservationId, memberId)
|
||||||
|
|
||||||
return paymentCancelRequest
|
return paymentCancelRequest
|
||||||
}
|
}
|
||||||
@ -48,7 +49,6 @@ class ReservationWithPaymentService(
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun isNotPaidReservation(reservationId: Long): Boolean = !paymentService.isReservationPaid(reservationId)
|
fun isNotPaidReservation(reservationId: Long): Boolean = !paymentService.isReservationPaid(reservationId)
|
||||||
|
|
||||||
|
|
||||||
fun updateCanceledTime(
|
fun updateCanceledTime(
|
||||||
paymentKey: String,
|
paymentKey: String,
|
||||||
canceledAt: OffsetDateTime
|
canceledAt: OffsetDateTime
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
package roomescape.reservation.business
|
||||||
|
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import roomescape.common.exception.ErrorType
|
||||||
|
import roomescape.common.exception.RoomescapeException
|
||||||
|
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||||
|
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||||
|
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||||
|
import roomescape.reservation.infrastructure.persistence.TimeRepository
|
||||||
|
import roomescape.reservation.web.*
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class TimeService(
|
||||||
|
private val timeRepository: TimeRepository,
|
||||||
|
private val reservationRepository: ReservationRepository
|
||||||
|
) {
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun findById(id: Long): TimeEntity = timeRepository.findByIdOrNull(id)
|
||||||
|
?: throw RoomescapeException(
|
||||||
|
ErrorType.TIME_NOT_FOUND,
|
||||||
|
"[timeId: $id]",
|
||||||
|
HttpStatus.BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun findTimes(): TimeRetrieveListResponse = timeRepository.findAll().toRetrieveListResponse()
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun createTime(timeCreateRequest: TimeCreateRequest): TimeCreateResponse {
|
||||||
|
val startAt: LocalTime = timeCreateRequest.startAt
|
||||||
|
|
||||||
|
if (timeRepository.existsByStartAt(startAt)) {
|
||||||
|
throw RoomescapeException(
|
||||||
|
ErrorType.TIME_DUPLICATED, "[startAt: $startAt]", HttpStatus.CONFLICT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeEntity(startAt = startAt)
|
||||||
|
.also { timeRepository.save(it) }
|
||||||
|
.toCreateResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun deleteTime(id: Long) {
|
||||||
|
val time: TimeEntity = findById(id)
|
||||||
|
reservationRepository.findByTime(time)
|
||||||
|
.also {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
throw RoomescapeException(
|
||||||
|
ErrorType.TIME_IS_USED_CONFLICT, "[timeId: $id]", HttpStatus.CONFLICT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
timeRepository.deleteById(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
fun findTimesWithAvailability(date: LocalDate, themeId: Long): TimeWithAvailabilityListResponse {
|
||||||
|
val allTimes = timeRepository.findAll()
|
||||||
|
val reservations: List<ReservationEntity> = reservationRepository.findByDateAndThemeId(date, themeId)
|
||||||
|
|
||||||
|
return TimeWithAvailabilityListResponse(allTimes.map { time ->
|
||||||
|
val isAvailable: Boolean = reservations.none { reservation -> reservation.time.id == time.id }
|
||||||
|
|
||||||
|
TimeWithAvailabilityResponse(time.id!!, time.startAt, isAvailable)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,33 +26,33 @@ interface ReservationAPI {
|
|||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "모든 예약 정보 조회", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "모든 예약 정보 조회", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun getAllReservations(): ResponseEntity<CommonApiResponse<ReservationsResponse>>
|
fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
|
||||||
|
|
||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "자신의 예약 및 대기 조회", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "자신의 예약 및 대기 조회", tags = ["로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun getMemberReservations(
|
fun findReservationsByMemberId(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
@MemberId @Parameter(hidden = true) memberId: Long
|
||||||
): ResponseEntity<CommonApiResponse<MyReservationsResponse>>
|
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>>
|
||||||
|
|
||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "관리자의 예약 검색", description = "특정 조건에 해당되는 예약 검색", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "관리자의 예약 검색", description = "특정 조건에 해당되는 예약 검색", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
|
ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
|
||||||
)
|
)
|
||||||
fun getReservationBySearching(
|
fun searchReservations(
|
||||||
@RequestParam(required = false) themeId: Long?,
|
@RequestParam(required = false) themeId: Long?,
|
||||||
@RequestParam(required = false) memberId: Long?,
|
@RequestParam(required = false) memberId: Long?,
|
||||||
@RequestParam(required = false) dateFrom: LocalDate?,
|
@RequestParam(required = false) dateFrom: LocalDate?,
|
||||||
@RequestParam(required = false) dateTo: LocalDate?
|
@RequestParam(required = false) dateTo: LocalDate?
|
||||||
): ResponseEntity<CommonApiResponse<ReservationsResponse>>
|
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
|
||||||
|
|
||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "관리자의 예약 취소", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "관리자의 예약 취소", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
ApiResponse(responseCode = "204", description = "성공"),
|
ApiResponse(responseCode = "204", description = "성공"),
|
||||||
)
|
)
|
||||||
fun removeReservation(
|
fun cancelReservationByAdmin(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") reservationId: Long
|
@PathVariable("id") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
@ -67,10 +67,10 @@ interface ReservationAPI {
|
|||||||
headers = [Header(name = HttpHeaders.LOCATION, description = "생성된 예약 정보 URL", schema = Schema(example = "/reservations/1"))]
|
headers = [Header(name = HttpHeaders.LOCATION, description = "생성된 예약 정보 URL", schema = Schema(example = "/reservations/1"))]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fun saveReservation(
|
fun createReservationWithPayment(
|
||||||
@Valid @RequestBody reservationRequest: ReservationRequest,
|
@Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest,
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
@MemberId @Parameter(hidden = true) memberId: Long
|
||||||
): ResponseEntity<CommonApiResponse<ReservationResponse>>
|
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>>
|
||||||
|
|
||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "관리자 예약 추가", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "관리자 예약 추가", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ -82,14 +82,14 @@ interface ReservationAPI {
|
|||||||
headers = [Header(name = HttpHeaders.LOCATION, description = "생성된 예약 정보 URL", schema = Schema(example = "/reservations/1"))],
|
headers = [Header(name = HttpHeaders.LOCATION, description = "생성된 예약 정보 URL", schema = Schema(example = "/reservations/1"))],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fun saveReservationByAdmin(
|
fun createReservationByAdmin(
|
||||||
@Valid @RequestBody adminReservationRequest: AdminReservationRequest,
|
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest,
|
||||||
): ResponseEntity<CommonApiResponse<ReservationResponse>>
|
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>>
|
||||||
|
|
||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "모든 예약 대기 조회", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "모든 예약 대기 조회", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun getAllWaiting(): ResponseEntity<CommonApiResponse<ReservationsResponse>>
|
fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>>
|
||||||
|
|
||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "예약 대기 신청", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "예약 대기 신청", tags = ["로그인이 필요한 API"])
|
||||||
@ -101,17 +101,17 @@ interface ReservationAPI {
|
|||||||
headers = [Header(name = HttpHeaders.LOCATION, description = "생성된 예약 정보 URL", schema = Schema(example = "/reservations/1"))]
|
headers = [Header(name = HttpHeaders.LOCATION, description = "생성된 예약 정보 URL", schema = Schema(example = "/reservations/1"))]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fun saveWaiting(
|
fun createWaiting(
|
||||||
@Valid @RequestBody waitingRequest: WaitingRequest,
|
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
): ResponseEntity<CommonApiResponse<ReservationResponse>>
|
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>>
|
||||||
|
|
||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "예약 대기 취소", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "예약 대기 취소", tags = ["로그인이 필요한 API"])
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
ApiResponse(responseCode = "204", description = "성공"),
|
ApiResponse(responseCode = "204", description = "성공"),
|
||||||
)
|
)
|
||||||
fun deleteWaiting(
|
fun cancelWaitingByMember(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
@ -121,7 +121,7 @@ interface ReservationAPI {
|
|||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
ApiResponse(responseCode = "200", description = "성공"),
|
ApiResponse(responseCode = "200", description = "성공"),
|
||||||
)
|
)
|
||||||
fun approveWaiting(
|
fun confirmWaiting(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
@ -131,7 +131,7 @@ interface ReservationAPI {
|
|||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
ApiResponse(responseCode = "204", description = "대기 중인 예약 거절 성공"),
|
ApiResponse(responseCode = "204", description = "대기 중인 예약 거절 성공"),
|
||||||
)
|
)
|
||||||
fun denyWaiting(
|
fun rejectWaiting(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
@PathVariable("id") @Parameter(description = "예약 ID") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
@ -12,39 +12,39 @@ import org.springframework.web.bind.annotation.RequestParam
|
|||||||
import roomescape.auth.web.support.Admin
|
import roomescape.auth.web.support.Admin
|
||||||
import roomescape.auth.web.support.LoginRequired
|
import roomescape.auth.web.support.LoginRequired
|
||||||
import roomescape.common.dto.response.CommonApiResponse
|
import roomescape.common.dto.response.CommonApiResponse
|
||||||
import roomescape.reservation.web.ReservationTimeInfosResponse
|
import roomescape.reservation.web.TimeCreateRequest
|
||||||
import roomescape.reservation.web.ReservationTimeRequest
|
import roomescape.reservation.web.TimeCreateResponse
|
||||||
import roomescape.reservation.web.ReservationTimeResponse
|
import roomescape.reservation.web.TimeRetrieveListResponse
|
||||||
import roomescape.reservation.web.ReservationTimesResponse
|
import roomescape.reservation.web.TimeWithAvailabilityListResponse
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
@Tag(name = "4. 예약 시간 API", description = "예약 시간을 조회 / 추가 / 삭제할 때 사용합니다.")
|
@Tag(name = "4. 예약 시간 API", description = "예약 시간을 조회 / 추가 / 삭제할 때 사용합니다.")
|
||||||
interface ReservationTimeAPI {
|
interface TimeAPI {
|
||||||
|
|
||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "모든 시간 조회", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "모든 시간 조회", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun getAllTimes(): ResponseEntity<CommonApiResponse<ReservationTimesResponse>>
|
fun findTimes(): ResponseEntity<CommonApiResponse<TimeRetrieveListResponse>>
|
||||||
|
|
||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "시간 추가", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "시간 추가", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true))
|
||||||
fun saveTime(
|
fun createTime(
|
||||||
@Valid @RequestBody reservationTimeRequest: ReservationTimeRequest,
|
@Valid @RequestBody timeCreateRequest: TimeCreateRequest,
|
||||||
): ResponseEntity<CommonApiResponse<ReservationTimeResponse>>
|
): ResponseEntity<CommonApiResponse<TimeCreateResponse>>
|
||||||
|
|
||||||
@Admin
|
@Admin
|
||||||
@Operation(summary = "시간 삭제", tags = ["관리자 로그인이 필요한 API"])
|
@Operation(summary = "시간 삭제", tags = ["관리자 로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true))
|
||||||
fun removeTime(
|
fun deleteTime(
|
||||||
@PathVariable id: Long
|
@PathVariable id: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
|
|
||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "예약 가능 여부를 포함한 모든 시간 조회", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "예약 가능 여부를 포함한 모든 시간 조회", tags = ["로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun findAllAvailableReservationTimes(
|
fun findTimesWithAvailability(
|
||||||
@RequestParam date: LocalDate,
|
@RequestParam date: LocalDate,
|
||||||
@RequestParam themeId: Long
|
@RequestParam themeId: Long
|
||||||
): ResponseEntity<CommonApiResponse<ReservationTimeInfosResponse>>
|
): ResponseEntity<CommonApiResponse<TimeWithAvailabilityListResponse>>
|
||||||
}
|
}
|
||||||
@ -1,14 +1,13 @@
|
|||||||
package roomescape.reservation.infrastructure.persistence
|
package roomescape.reservation.infrastructure.persistence
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
|
||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "reservation")
|
@Table(name = "reservations")
|
||||||
class ReservationEntity(
|
class ReservationEntity(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@ -18,7 +17,7 @@ class ReservationEntity(
|
|||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name = "time_id", nullable = false)
|
@JoinColumn(name = "time_id", nullable = false)
|
||||||
var reservationTime: ReservationTimeEntity,
|
var time: TimeEntity,
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name = "theme_id", nullable = false)
|
@JoinColumn(name = "theme_id", nullable = false)
|
||||||
@ -40,14 +39,8 @@ class ReservationEntity(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema(description = "예약 상태를 나타냅니다.", allowableValues = ["CONFIRMED", "CONFIRMED_PAYMENT_REQUIRED", "WAITING"])
|
|
||||||
enum class ReservationStatus {
|
enum class ReservationStatus {
|
||||||
@Schema(description = "결제가 완료된 예약")
|
|
||||||
CONFIRMED,
|
CONFIRMED,
|
||||||
|
|
||||||
@Schema(description = "결제가 필요한 예약")
|
|
||||||
CONFIRMED_PAYMENT_REQUIRED,
|
CONFIRMED_PAYMENT_REQUIRED,
|
||||||
|
|
||||||
@Schema(description = "대기 중인 예약")
|
|
||||||
WAITING
|
WAITING
|
||||||
}
|
}
|
||||||
@ -5,12 +5,12 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor
|
|||||||
import org.springframework.data.jpa.repository.Modifying
|
import org.springframework.data.jpa.repository.Modifying
|
||||||
import org.springframework.data.jpa.repository.Query
|
import org.springframework.data.jpa.repository.Query
|
||||||
import org.springframework.data.repository.query.Param
|
import org.springframework.data.repository.query.Param
|
||||||
import roomescape.reservation.web.MyReservationResponse
|
import roomescape.reservation.web.MyReservationRetrieveResponse
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
interface ReservationRepository
|
interface ReservationRepository
|
||||||
: JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> {
|
: JpaRepository<ReservationEntity, Long>, JpaSpecificationExecutor<ReservationEntity> {
|
||||||
fun findByReservationTime(reservationTime: ReservationTimeEntity): List<ReservationEntity>
|
fun findByTime(time: TimeEntity): List<ReservationEntity>
|
||||||
|
|
||||||
fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity>
|
fun findByDateAndThemeId(date: LocalDate, themeId: Long): List<ReservationEntity>
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ interface ReservationRepository
|
|||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1 FROM ReservationEntity r
|
SELECT 1 FROM ReservationEntity r
|
||||||
WHERE r.theme.id = r2.theme.id
|
WHERE r.theme.id = r2.theme.id
|
||||||
AND r.reservationTime.id = r2.reservationTime.id
|
AND r.time.id = r2.time.id
|
||||||
AND r.date = r2.date
|
AND r.date = r2.date
|
||||||
AND r.reservationStatus != 'WAITING'
|
AND r.reservationStatus != 'WAITING'
|
||||||
)
|
)
|
||||||
@ -42,13 +42,13 @@ interface ReservationRepository
|
|||||||
fun isExistConfirmedReservation(@Param("id") reservationId: Long): Boolean
|
fun isExistConfirmedReservation(@Param("id") reservationId: Long): Boolean
|
||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT new roomescape.reservation.web.MyReservationResponse(
|
SELECT new roomescape.reservation.web.MyReservationRetrieveResponse(
|
||||||
r.id,
|
r.id,
|
||||||
t.name,
|
t.name,
|
||||||
r.date,
|
r.date,
|
||||||
r.reservationTime.startAt,
|
r.time.startAt,
|
||||||
r.reservationStatus,
|
r.reservationStatus,
|
||||||
(SELECT COUNT (r2) * 1L FROM ReservationEntity r2 WHERE r2.theme = r.theme AND r2.date = r.date AND r2.reservationTime = r.reservationTime AND r2.id < r.id),
|
(SELECT COUNT (r2) * 1L FROM ReservationEntity r2 WHERE r2.theme = r.theme AND r2.date = r.date AND r2.time = r.time AND r2.id < r.id),
|
||||||
p.paymentKey,
|
p.paymentKey,
|
||||||
p.totalAmount
|
p.totalAmount
|
||||||
)
|
)
|
||||||
@ -58,5 +58,5 @@ interface ReservationRepository
|
|||||||
ON p.reservation = r
|
ON p.reservation = r
|
||||||
WHERE r.member.id = :memberId
|
WHERE r.member.id = :memberId
|
||||||
""")
|
""")
|
||||||
fun findMyReservations(memberId: Long): List<MyReservationResponse>
|
fun findAllById(memberId: Long): List<MyReservationRetrieveResponse>
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ class ReservationSearchSpecification(
|
|||||||
|
|
||||||
fun sameTimeId(timeId: Long?): ReservationSearchSpecification = andIfNotNull(timeId?.let {
|
fun sameTimeId(timeId: Long?): ReservationSearchSpecification = andIfNotNull(timeId?.let {
|
||||||
Specification { root, _, cb ->
|
Specification { root, _, cb ->
|
||||||
cb.equal(root.get<ReservationTimeEntity>("reservationTime").get<Long>("id"), timeId)
|
cb.equal(root.get<TimeEntity>("time").get<Long>("id"), timeId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -4,8 +4,8 @@ import jakarta.persistence.*
|
|||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "reservation_time")
|
@Table(name = "times")
|
||||||
class ReservationTimeEntity(
|
class TimeEntity(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
var id: Long? = null,
|
var id: Long? = null,
|
||||||
@ -3,6 +3,6 @@ package roomescape.reservation.infrastructure.persistence
|
|||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
interface ReservationTimeRepository : JpaRepository<ReservationTimeEntity, Long> {
|
interface TimeRepository : JpaRepository<TimeEntity, Long> {
|
||||||
fun existsByStartAt(startAt: LocalTime): Boolean
|
fun existsByStartAt(startAt: LocalTime): Boolean
|
||||||
}
|
}
|
||||||
@ -7,9 +7,10 @@ import org.springframework.web.bind.annotation.*
|
|||||||
import roomescape.auth.web.support.MemberId
|
import roomescape.auth.web.support.MemberId
|
||||||
import roomescape.common.dto.response.CommonApiResponse
|
import roomescape.common.dto.response.CommonApiResponse
|
||||||
import roomescape.common.exception.RoomescapeException
|
import roomescape.common.exception.RoomescapeException
|
||||||
|
import roomescape.payment.infrastructure.client.PaymentApproveRequest
|
||||||
|
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
||||||
import roomescape.payment.infrastructure.client.TossPaymentClient
|
import roomescape.payment.infrastructure.client.TossPaymentClient
|
||||||
import roomescape.payment.web.PaymentApprove
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
import roomescape.payment.web.PaymentCancel
|
|
||||||
import roomescape.reservation.business.ReservationService
|
import roomescape.reservation.business.ReservationService
|
||||||
import roomescape.reservation.business.ReservationWithPaymentService
|
import roomescape.reservation.business.ReservationWithPaymentService
|
||||||
import roomescape.reservation.docs.ReservationAPI
|
import roomescape.reservation.docs.ReservationAPI
|
||||||
@ -23,46 +24,50 @@ class ReservationController(
|
|||||||
private val paymentClient: TossPaymentClient
|
private val paymentClient: TossPaymentClient
|
||||||
) : ReservationAPI {
|
) : ReservationAPI {
|
||||||
@GetMapping("/reservations")
|
@GetMapping("/reservations")
|
||||||
override fun getAllReservations(): ResponseEntity<CommonApiResponse<ReservationsResponse>> {
|
override fun findReservations(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
||||||
val response: ReservationsResponse = reservationService.findAllReservations()
|
val response: ReservationRetrieveListResponse = reservationService.findReservations()
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/reservations-mine")
|
@GetMapping("/reservations-mine")
|
||||||
override fun getMemberReservations(
|
override fun findReservationsByMemberId(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
@MemberId @Parameter(hidden = true) memberId: Long
|
||||||
): ResponseEntity<CommonApiResponse<MyReservationsResponse>> {
|
): ResponseEntity<CommonApiResponse<MyReservationRetrieveListResponse>> {
|
||||||
val response: MyReservationsResponse = reservationService.findMemberReservations(memberId)
|
val response: MyReservationRetrieveListResponse = reservationService.findReservationsByMemberId(memberId)
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/reservations/search")
|
@GetMapping("/reservations/search")
|
||||||
override fun getReservationBySearching(
|
override fun searchReservations(
|
||||||
@RequestParam(required = false) themeId: Long?,
|
@RequestParam(required = false) themeId: Long?,
|
||||||
@RequestParam(required = false) memberId: Long?,
|
@RequestParam(required = false) memberId: Long?,
|
||||||
@RequestParam(required = false) dateFrom: LocalDate?,
|
@RequestParam(required = false) dateFrom: LocalDate?,
|
||||||
@RequestParam(required = false) dateTo: LocalDate?
|
@RequestParam(required = false) dateTo: LocalDate?
|
||||||
): ResponseEntity<CommonApiResponse<ReservationsResponse>> {
|
): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
||||||
val response: ReservationsResponse = reservationService.findFilteredReservations(themeId, memberId, dateFrom, dateTo)
|
val response: ReservationRetrieveListResponse = reservationService.searchReservations(
|
||||||
|
themeId,
|
||||||
|
memberId,
|
||||||
|
dateFrom,
|
||||||
|
dateTo
|
||||||
|
)
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/reservations/{id}")
|
@DeleteMapping("/reservations/{id}")
|
||||||
override fun removeReservation(
|
override fun cancelReservationByAdmin(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") reservationId: Long
|
@PathVariable("id") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
if (reservationWithPaymentService.isNotPaidReservation(reservationId)) {
|
if (reservationWithPaymentService.isNotPaidReservation(reservationId)) {
|
||||||
reservationService.removeReservationById(reservationId, memberId)
|
reservationService.deleteReservation(reservationId, memberId)
|
||||||
return ResponseEntity.noContent().build()
|
return ResponseEntity.noContent().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
val paymentCancelRequest = reservationWithPaymentService.removeReservationWithPayment(
|
val paymentCancelRequest = reservationWithPaymentService.deleteReservationAndPayment(reservationId, memberId)
|
||||||
reservationId, memberId)
|
val paymentCancelResponse = paymentClient.cancel(paymentCancelRequest)
|
||||||
val paymentCancelResponse = paymentClient.cancelPayment(paymentCancelRequest)
|
|
||||||
reservationWithPaymentService.updateCanceledTime(paymentCancelRequest.paymentKey,
|
reservationWithPaymentService.updateCanceledTime(paymentCancelRequest.paymentKey,
|
||||||
paymentCancelResponse.canceledAt)
|
paymentCancelResponse.canceledAt)
|
||||||
|
|
||||||
@ -70,56 +75,56 @@ class ReservationController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/reservations")
|
@PostMapping("/reservations")
|
||||||
override fun saveReservation(
|
override fun createReservationWithPayment(
|
||||||
@Valid @RequestBody reservationRequest: ReservationRequest,
|
@Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest,
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long
|
@MemberId @Parameter(hidden = true) memberId: Long
|
||||||
): ResponseEntity<CommonApiResponse<ReservationResponse>> {
|
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>> {
|
||||||
val paymentRequest: PaymentApprove.Request = reservationRequest.paymentRequest
|
val paymentRequest: PaymentApproveRequest = reservationCreateWithPaymentRequest.toPaymentApproveRequest()
|
||||||
val paymentResponse: PaymentApprove.Response = paymentClient.confirmPayment(paymentRequest)
|
val paymentResponse: PaymentApproveResponse = paymentClient.confirm(paymentRequest)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val reservationResponse: ReservationResponse = reservationWithPaymentService.addReservationWithPayment(
|
val reservationRetrieveResponse: ReservationRetrieveResponse = reservationWithPaymentService.createReservationAndPayment(
|
||||||
reservationRequest,
|
reservationCreateWithPaymentRequest,
|
||||||
paymentResponse,
|
paymentResponse,
|
||||||
memberId
|
memberId
|
||||||
)
|
)
|
||||||
return ResponseEntity.created(URI.create("/reservations/${reservationResponse.id}"))
|
return ResponseEntity.created(URI.create("/reservations/${reservationRetrieveResponse.id}"))
|
||||||
.body(CommonApiResponse(reservationResponse))
|
.body(CommonApiResponse(reservationRetrieveResponse))
|
||||||
} catch (e: RoomescapeException) {
|
} catch (e: RoomescapeException) {
|
||||||
val cancelRequest = PaymentCancel.Request(paymentRequest.paymentKey,
|
val cancelRequest = PaymentCancelRequest(paymentRequest.paymentKey,
|
||||||
paymentRequest.amount, e.message!!)
|
paymentRequest.amount, e.message!!)
|
||||||
val paymentCancelResponse = paymentClient.cancelPayment(cancelRequest)
|
val paymentCancelResponse = paymentClient.cancel(cancelRequest)
|
||||||
reservationWithPaymentService.saveCanceledPayment(paymentCancelResponse, paymentResponse.approvedAt,
|
reservationWithPaymentService.createCanceledPayment(paymentCancelResponse, paymentResponse.approvedAt,
|
||||||
paymentRequest.paymentKey)
|
paymentRequest.paymentKey)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/reservations/admin")
|
@PostMapping("/reservations/admin")
|
||||||
override fun saveReservationByAdmin(
|
override fun createReservationByAdmin(
|
||||||
@Valid @RequestBody adminReservationRequest: AdminReservationRequest
|
@Valid @RequestBody adminReservationRequest: AdminReservationCreateRequest
|
||||||
): ResponseEntity<CommonApiResponse<ReservationResponse>> {
|
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>> {
|
||||||
val response: ReservationResponse =
|
val response: ReservationRetrieveResponse =
|
||||||
reservationService.addReservationByAdmin(adminReservationRequest)
|
reservationService.createReservationByAdmin(adminReservationRequest)
|
||||||
|
|
||||||
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
|
return ResponseEntity.created(URI.create("/reservations/${response.id}"))
|
||||||
.body(CommonApiResponse(response))
|
.body(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/reservations/waiting")
|
@GetMapping("/reservations/waiting")
|
||||||
override fun getAllWaiting(): ResponseEntity<CommonApiResponse<ReservationsResponse>> {
|
override fun findAllWaiting(): ResponseEntity<CommonApiResponse<ReservationRetrieveListResponse>> {
|
||||||
val response: ReservationsResponse = reservationService.findAllWaiting()
|
val response: ReservationRetrieveListResponse = reservationService.findAllWaiting()
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/reservations/waiting")
|
@PostMapping("/reservations/waiting")
|
||||||
override fun saveWaiting(
|
override fun createWaiting(
|
||||||
@Valid @RequestBody waitingRequest: WaitingRequest,
|
@Valid @RequestBody waitingCreateRequest: WaitingCreateRequest,
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
): ResponseEntity<CommonApiResponse<ReservationResponse>> {
|
): ResponseEntity<CommonApiResponse<ReservationRetrieveResponse>> {
|
||||||
val response: ReservationResponse = reservationService.addWaiting(
|
val response: ReservationRetrieveResponse = reservationService.createWaiting(
|
||||||
waitingRequest,
|
waitingCreateRequest,
|
||||||
memberId
|
memberId
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -128,31 +133,31 @@ class ReservationController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/reservations/waiting/{id}")
|
@DeleteMapping("/reservations/waiting/{id}")
|
||||||
override fun deleteWaiting(
|
override fun cancelWaitingByMember(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") reservationId: Long
|
@PathVariable("id") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
reservationService.cancelWaiting(reservationId, memberId)
|
reservationService.deleteWaiting(reservationId, memberId)
|
||||||
|
|
||||||
return ResponseEntity.noContent().build()
|
return ResponseEntity.noContent().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/reservations/waiting/{id}/approve")
|
@PostMapping("/reservations/waiting/{id}/confirm")
|
||||||
override fun approveWaiting(
|
override fun confirmWaiting(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") reservationId: Long
|
@PathVariable("id") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
reservationService.approveWaiting(reservationId, memberId)
|
reservationService.confirmWaiting(reservationId, memberId)
|
||||||
|
|
||||||
return ResponseEntity.ok().build()
|
return ResponseEntity.ok().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/reservations/waiting/{id}/deny")
|
@PostMapping("/reservations/waiting/{id}/reject")
|
||||||
override fun denyWaiting(
|
override fun rejectWaiting(
|
||||||
@MemberId @Parameter(hidden = true) memberId: Long,
|
@MemberId @Parameter(hidden = true) memberId: Long,
|
||||||
@PathVariable("id") reservationId: Long
|
@PathVariable("id") reservationId: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
reservationService.denyWaiting(reservationId, memberId)
|
reservationService.rejectWaiting(reservationId, memberId)
|
||||||
|
|
||||||
return ResponseEntity.noContent().build()
|
return ResponseEntity.noContent().build()
|
||||||
}
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package roomescape.reservation.web
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
|
import roomescape.payment.infrastructure.client.PaymentApproveRequest
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
data class AdminReservationCreateRequest(
|
||||||
|
val date: LocalDate,
|
||||||
|
val timeId: Long,
|
||||||
|
val themeId: Long,
|
||||||
|
val memberId: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReservationCreateWithPaymentRequest(
|
||||||
|
val date: LocalDate,
|
||||||
|
val timeId: Long,
|
||||||
|
val themeId: Long,
|
||||||
|
|
||||||
|
@field:Schema(description = "결제 위젯을 통해 받은 결제 키")
|
||||||
|
val paymentKey: String,
|
||||||
|
|
||||||
|
@field:Schema(description = "결제 위젯을 통해 받은 주문번호.")
|
||||||
|
val orderId: String,
|
||||||
|
|
||||||
|
@field:Schema(description = "결제 위젯을 통해 받은 결제 금액")
|
||||||
|
val amount: Long,
|
||||||
|
|
||||||
|
@field:Schema(description = "결제 타입", example = "NORMAL")
|
||||||
|
val paymentType: String
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ReservationCreateWithPaymentRequest.toPaymentApproveRequest(): PaymentApproveRequest = PaymentApproveRequest(
|
||||||
|
paymentKey, orderId, amount, paymentType
|
||||||
|
)
|
||||||
|
|
||||||
|
data class WaitingCreateRequest(
|
||||||
|
val date: LocalDate,
|
||||||
|
val timeId: Long,
|
||||||
|
val themeId: Long
|
||||||
|
)
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
package roomescape.reservation.web
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
|
import roomescape.member.web.MemberRetrieveResponse
|
||||||
|
import roomescape.member.web.toRetrieveResponse
|
||||||
|
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||||
|
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||||
|
import roomescape.theme.web.ThemeResponse
|
||||||
|
import roomescape.theme.web.toResponse
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
|
data class MyReservationRetrieveResponse(
|
||||||
|
val id: Long,
|
||||||
|
val themeName: String,
|
||||||
|
val date: LocalDate,
|
||||||
|
val time: LocalTime,
|
||||||
|
val status: ReservationStatus,
|
||||||
|
@field:Schema(description = "대기 순번. 확정된 예약은 0의 값을 가집니다.")
|
||||||
|
val rank: Long,
|
||||||
|
@field:Schema(description = "결제 키. 결제가 완료된 예약에만 값이 존재합니다.")
|
||||||
|
val paymentKey: String?,
|
||||||
|
@field:Schema(description = "결제 금액. 결제가 완료된 예약에만 값이 존재합니다.")
|
||||||
|
val amount: Long?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MyReservationRetrieveListResponse(
|
||||||
|
@field:Schema(description = "현재 로그인한 회원의 예약 및 대기 목록")
|
||||||
|
val reservations: List<MyReservationRetrieveResponse>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReservationRetrieveResponse(
|
||||||
|
val id: Long,
|
||||||
|
val date: LocalDate,
|
||||||
|
|
||||||
|
@field:JsonProperty("member")
|
||||||
|
val member: MemberRetrieveResponse,
|
||||||
|
|
||||||
|
@field:JsonProperty("time")
|
||||||
|
val time: TimeCreateResponse,
|
||||||
|
|
||||||
|
@field:JsonProperty("theme")
|
||||||
|
val theme: ThemeResponse,
|
||||||
|
|
||||||
|
val status: ReservationStatus
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ReservationEntity.toRetrieveResponse(): ReservationRetrieveResponse = ReservationRetrieveResponse(
|
||||||
|
id = this.id!!,
|
||||||
|
date = this.date,
|
||||||
|
member = this.member.toRetrieveResponse(),
|
||||||
|
time = this.time.toCreateResponse(),
|
||||||
|
theme = this.theme.toResponse(),
|
||||||
|
status = this.reservationStatus
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ReservationRetrieveListResponse(
|
||||||
|
val reservations: List<ReservationRetrieveResponse>
|
||||||
|
)
|
||||||
51
src/main/kotlin/roomescape/reservation/web/TimeController.kt
Normal file
51
src/main/kotlin/roomescape/reservation/web/TimeController.kt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package roomescape.reservation.web
|
||||||
|
|
||||||
|
import jakarta.validation.Valid
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.bind.annotation.*
|
||||||
|
import roomescape.common.dto.response.CommonApiResponse
|
||||||
|
import roomescape.reservation.business.TimeService
|
||||||
|
import roomescape.reservation.docs.TimeAPI
|
||||||
|
import java.net.URI
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
class TimeController(
|
||||||
|
private val timeService: TimeService
|
||||||
|
) : TimeAPI {
|
||||||
|
|
||||||
|
@GetMapping("/times")
|
||||||
|
override fun findTimes(): ResponseEntity<CommonApiResponse<TimeRetrieveListResponse>> {
|
||||||
|
val response: TimeRetrieveListResponse = timeService.findTimes()
|
||||||
|
|
||||||
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/times")
|
||||||
|
override fun createTime(
|
||||||
|
@Valid @RequestBody timeCreateRequest: TimeCreateRequest,
|
||||||
|
): ResponseEntity<CommonApiResponse<TimeCreateResponse>> {
|
||||||
|
val response: TimeCreateResponse = timeService.createTime(timeCreateRequest)
|
||||||
|
|
||||||
|
return ResponseEntity
|
||||||
|
.created(URI.create("/times/${response.id}"))
|
||||||
|
.body(CommonApiResponse(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/times/{id}")
|
||||||
|
override fun deleteTime(@PathVariable id: Long): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
|
timeService.deleteTime(id)
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/times/search")
|
||||||
|
override fun findTimesWithAvailability(
|
||||||
|
@RequestParam date: LocalDate,
|
||||||
|
@RequestParam themeId: Long
|
||||||
|
): ResponseEntity<CommonApiResponse<TimeWithAvailabilityListResponse>> {
|
||||||
|
val response: TimeWithAvailabilityListResponse = timeService.findTimesWithAvailability(date, themeId)
|
||||||
|
|
||||||
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/main/kotlin/roomescape/reservation/web/TimeDTO.kt
Normal file
55
src/main/kotlin/roomescape/reservation/web/TimeDTO.kt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package roomescape.reservation.web
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
|
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
|
@Schema(name = "예약 시간 저장 요청", description = "예약 시간 저장 요청시 사용됩니다.")
|
||||||
|
data class TimeCreateRequest(
|
||||||
|
@field:Schema(description = "시간", type = "string", example = "09:00")
|
||||||
|
val startAt: LocalTime
|
||||||
|
)
|
||||||
|
|
||||||
|
@Schema(name = "예약 시간 정보", description = "예약 시간 추가 및 조회 응답시 사용됩니다.")
|
||||||
|
data class TimeCreateResponse(
|
||||||
|
@field:Schema(description = "시간 식별자")
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@field:Schema(description = "시간")
|
||||||
|
val startAt: LocalTime
|
||||||
|
)
|
||||||
|
|
||||||
|
fun TimeEntity.toCreateResponse(): TimeCreateResponse = TimeCreateResponse(this.id!!, this.startAt)
|
||||||
|
|
||||||
|
data class TimeRetrieveResponse(
|
||||||
|
@field:Schema(description = "시간 식별자.")
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@field:Schema(description = "시간")
|
||||||
|
val startAt: LocalTime
|
||||||
|
)
|
||||||
|
|
||||||
|
fun TimeEntity.toRetrieveResponse(): TimeRetrieveResponse = TimeRetrieveResponse(this.id!!, this.startAt)
|
||||||
|
|
||||||
|
data class TimeRetrieveListResponse(
|
||||||
|
val times: List<TimeRetrieveResponse>
|
||||||
|
)
|
||||||
|
|
||||||
|
fun List<TimeEntity>.toRetrieveListResponse(): TimeRetrieveListResponse = TimeRetrieveListResponse(
|
||||||
|
this.map { it.toRetrieveResponse() }
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TimeWithAvailabilityResponse(
|
||||||
|
@field:Schema(description = "시간 식별자")
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
@field:Schema(description = "시간")
|
||||||
|
val startAt: LocalTime,
|
||||||
|
|
||||||
|
@field:Schema(description = "예약 가능 여부")
|
||||||
|
val isAvailable: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TimeWithAvailabilityListResponse(
|
||||||
|
val times: List<TimeWithAvailabilityResponse>
|
||||||
|
)
|
||||||
@ -19,7 +19,7 @@ class ThemeService(
|
|||||||
private val themeRepository: ThemeRepository
|
private val themeRepository: ThemeRepository
|
||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findThemeById(id: Long): ThemeEntity = themeRepository.findByIdOrNull(id)
|
fun findById(id: Long): ThemeEntity = themeRepository.findByIdOrNull(id)
|
||||||
?: throw RoomescapeException(
|
?: throw RoomescapeException(
|
||||||
ErrorType.THEME_NOT_FOUND,
|
ErrorType.THEME_NOT_FOUND,
|
||||||
"[themeId: $id]",
|
"[themeId: $id]",
|
||||||
@ -27,22 +27,21 @@ class ThemeService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findAllThemes(): ThemesResponse = themeRepository.findAll()
|
fun findThemes(): ThemesResponse = themeRepository.findAll()
|
||||||
.toResponse()
|
.toResponse()
|
||||||
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getMostReservedThemesByCount(count: Int): ThemesResponse {
|
fun findMostReservedThemes(count: Int): ThemesResponse {
|
||||||
val today = LocalDate.now()
|
val today = LocalDate.now()
|
||||||
val startDate = today.minusDays(7)
|
val startDate = today.minusDays(7)
|
||||||
val endDate = today.minusDays(1)
|
val endDate = today.minusDays(1)
|
||||||
|
|
||||||
return themeRepository.findTopNThemeBetweenStartDateAndEndDate(startDate, endDate, count)
|
return themeRepository.findPopularThemes(startDate, endDate, count)
|
||||||
.toResponse()
|
.toResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun save(request: ThemeRequest): ThemeResponse {
|
fun createTheme(request: ThemeRequest): ThemeResponse {
|
||||||
if (themeRepository.existsByName(request.name)) {
|
if (themeRepository.existsByName(request.name)) {
|
||||||
throw RoomescapeException(
|
throw RoomescapeException(
|
||||||
ErrorType.THEME_DUPLICATED,
|
ErrorType.THEME_DUPLICATED,
|
||||||
@ -61,7 +60,7 @@ class ThemeService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun deleteById(id: Long) {
|
fun deleteTheme(id: Long) {
|
||||||
if (themeRepository.isReservedTheme(id)) {
|
if (themeRepository.isReservedTheme(id)) {
|
||||||
throw RoomescapeException(
|
throw RoomescapeException(
|
||||||
ErrorType.THEME_IS_USED_CONFLICT,
|
ErrorType.THEME_IS_USED_CONFLICT,
|
||||||
@ -23,11 +23,11 @@ interface ThemeAPI {
|
|||||||
@LoginRequired
|
@LoginRequired
|
||||||
@Operation(summary = "모든 테마 조회", description = "모든 테마를 조회합니다.", tags = ["로그인이 필요한 API"])
|
@Operation(summary = "모든 테마 조회", description = "모든 테마를 조회합니다.", tags = ["로그인이 필요한 API"])
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun getAllThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>>
|
fun findThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>>
|
||||||
|
|
||||||
@Operation(summary = "가장 많이 예약된 테마 조회")
|
@Operation(summary = "가장 많이 예약된 테마 조회")
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
|
||||||
fun getMostReservedThemes(
|
fun findMostReservedThemes(
|
||||||
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
|
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
|
||||||
): ResponseEntity<CommonApiResponse<ThemesResponse>>
|
): ResponseEntity<CommonApiResponse<ThemesResponse>>
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ interface ThemeAPI {
|
|||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true),
|
ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true),
|
||||||
)
|
)
|
||||||
fun saveTheme(
|
fun createTheme(
|
||||||
@Valid @RequestBody request: ThemeRequest,
|
@Valid @RequestBody request: ThemeRequest,
|
||||||
): ResponseEntity<CommonApiResponse<ThemeResponse>>
|
): ResponseEntity<CommonApiResponse<ThemeResponse>>
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ interface ThemeAPI {
|
|||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true),
|
ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true),
|
||||||
)
|
)
|
||||||
fun removeTheme(
|
fun deleteTheme(
|
||||||
@PathVariable id: Long
|
@PathVariable id: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>>
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
}
|
}
|
||||||
@ -3,7 +3,7 @@ package roomescape.theme.infrastructure.persistence
|
|||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "theme")
|
@Table(name = "themes")
|
||||||
class ThemeEntity(
|
class ThemeEntity(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@ -14,10 +14,9 @@ interface ThemeRepository : JpaRepository<ThemeEntity, Long> {
|
|||||||
GROUP BY r.theme.id
|
GROUP BY r.theme.id
|
||||||
ORDER BY COUNT(r.theme.id) DESC, t.id ASC
|
ORDER BY COUNT(r.theme.id) DESC, t.id ASC
|
||||||
LIMIT :limit
|
LIMIT :limit
|
||||||
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
fun findTopNThemeBetweenStartDateAndEndDate(startDate: LocalDate, endDate: LocalDate, limit: Int): List<ThemeEntity>
|
fun findPopularThemes(startDate: LocalDate, endDate: LocalDate, limit: Int): List<ThemeEntity>
|
||||||
|
|
||||||
fun existsByName(name: String): Boolean
|
fun existsByName(name: String): Boolean
|
||||||
|
|
||||||
@ -15,36 +15,36 @@ class ThemeController(
|
|||||||
) : ThemeAPI {
|
) : ThemeAPI {
|
||||||
|
|
||||||
@GetMapping("/themes")
|
@GetMapping("/themes")
|
||||||
override fun getAllThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>> {
|
override fun findThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>> {
|
||||||
val response: ThemesResponse = themeService.findAllThemes()
|
val response: ThemesResponse = themeService.findThemes()
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/themes/most-reserved-last-week")
|
@GetMapping("/themes/most-reserved-last-week")
|
||||||
override fun getMostReservedThemes(
|
override fun findMostReservedThemes(
|
||||||
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
|
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
|
||||||
): ResponseEntity<CommonApiResponse<ThemesResponse>> {
|
): ResponseEntity<CommonApiResponse<ThemesResponse>> {
|
||||||
val response: ThemesResponse = themeService.getMostReservedThemesByCount(count)
|
val response: ThemesResponse = themeService.findMostReservedThemes(count)
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/themes")
|
@PostMapping("/themes")
|
||||||
override fun saveTheme(
|
override fun createTheme(
|
||||||
@RequestBody @Valid request: ThemeRequest
|
@RequestBody @Valid request: ThemeRequest
|
||||||
): ResponseEntity<CommonApiResponse<ThemeResponse>> {
|
): ResponseEntity<CommonApiResponse<ThemeResponse>> {
|
||||||
val themeResponse: ThemeResponse = themeService.save(request)
|
val themeResponse: ThemeResponse = themeService.createTheme(request)
|
||||||
|
|
||||||
return ResponseEntity.created(URI.create("/themes/${themeResponse.id}"))
|
return ResponseEntity.created(URI.create("/themes/${themeResponse.id}"))
|
||||||
.body(CommonApiResponse(themeResponse))
|
.body(CommonApiResponse(themeResponse))
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/themes/{id}")
|
@DeleteMapping("/themes/{id}")
|
||||||
override fun removeTheme(
|
override fun deleteTheme(
|
||||||
@PathVariable id: Long
|
@PathVariable id: Long
|
||||||
): ResponseEntity<CommonApiResponse<Unit>> {
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
themeService.deleteById(id)
|
themeService.deleteTheme(id)
|
||||||
|
|
||||||
return ResponseEntity.noContent().build()
|
return ResponseEntity.noContent().build()
|
||||||
}
|
}
|
||||||
@ -7,7 +7,6 @@ import org.hibernate.validator.constraints.URL
|
|||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
|
|
||||||
@Schema(name = "테마 저장 요청", description = "테마 정보를 저장할 때 사용합니다.")
|
@Schema(name = "테마 저장 요청", description = "테마 정보를 저장할 때 사용합니다.")
|
||||||
@JvmRecord
|
|
||||||
data class ThemeRequest(
|
data class ThemeRequest(
|
||||||
@field:Schema(description = "필수 값이며, 최대 20글자까지 입력 가능합니다.")
|
@field:Schema(description = "필수 값이며, 최대 20글자까지 입력 가능합니다.")
|
||||||
@NotBlank
|
@NotBlank
|
||||||
@ -26,7 +25,6 @@ data class ThemeRequest(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Schema(name = "테마 정보", description = "테마 추가 및 조회 응답에 사용됩니다.")
|
@Schema(name = "테마 정보", description = "테마 추가 및 조회 응답에 사용됩니다.")
|
||||||
@JvmRecord
|
|
||||||
data class ThemeResponse(
|
data class ThemeResponse(
|
||||||
@field:Schema(description = "테마 번호. 테마를 식별할 때 사용합니다.")
|
@field:Schema(description = "테마 번호. 테마를 식별할 때 사용합니다.")
|
||||||
val id: Long,
|
val id: Long,
|
||||||
@ -39,14 +37,7 @@ data class ThemeResponse(
|
|||||||
|
|
||||||
@field:Schema(description = "테마 썸네일 이미지 URL")
|
@field:Schema(description = "테마 썸네일 이미지 URL")
|
||||||
val thumbnail: String
|
val thumbnail: String
|
||||||
) {
|
)
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun from(themeEntity: ThemeEntity): ThemeResponse {
|
|
||||||
return ThemeResponse(themeEntity.id!!, themeEntity.name, themeEntity.description, themeEntity.thumbnail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ThemeEntity.toResponse(): ThemeResponse = ThemeResponse(
|
fun ThemeEntity.toResponse(): ThemeResponse = ThemeResponse(
|
||||||
id = this.id!!,
|
id = this.id!!,
|
||||||
@ -55,9 +46,7 @@ fun ThemeEntity.toResponse(): ThemeResponse = ThemeResponse(
|
|||||||
thumbnail = this.thumbnail
|
thumbnail = this.thumbnail
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@Schema(name = "테마 목록 조회 응답", description = "모든 테마 목록 조회 응답시 사용됩니다.")
|
@Schema(name = "테마 목록 조회 응답", description = "모든 테마 목록 조회 응답시 사용됩니다.")
|
||||||
@JvmRecord
|
|
||||||
data class ThemesResponse(
|
data class ThemesResponse(
|
||||||
@field:Schema(description = "모든 테마 목록")
|
@field:Schema(description = "모든 테마 목록")
|
||||||
val themes: List<ThemeResponse>
|
val themes: List<ThemeResponse>
|
||||||
@ -1,68 +1,68 @@
|
|||||||
insert into reservation_time(start_at)
|
insert into times(start_at)
|
||||||
values ('15:00');
|
values ('15:00');
|
||||||
insert into reservation_time(start_at)
|
insert into times(start_at)
|
||||||
values ('16:00');
|
values ('16:00');
|
||||||
insert into reservation_time(start_at)
|
insert into times(start_at)
|
||||||
values ('17:00');
|
values ('17:00');
|
||||||
insert into reservation_time(start_at)
|
insert into times(start_at)
|
||||||
values ('18:00');
|
values ('18:00');
|
||||||
|
|
||||||
insert into theme(name, description, thumbnail)
|
insert into themes(name, description, thumbnail)
|
||||||
values ('테스트1', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
values ('테스트1', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
||||||
insert into theme(name, description, thumbnail)
|
insert into themes(name, description, thumbnail)
|
||||||
values ('테스트2', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
values ('테스트2', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
||||||
insert into theme(name, description, thumbnail)
|
insert into themes(name, description, thumbnail)
|
||||||
values ('테스트3', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
values ('테스트3', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
||||||
insert into theme(name, description, thumbnail)
|
insert into themes(name, description, thumbnail)
|
||||||
values ('테스트4', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
values ('테스트4', '테스트중', 'https://i.pinimg.com/236x/6e/bc/46/6ebc461a94a49f9ea3b8bbe2204145d4.jpg');
|
||||||
|
|
||||||
insert into member(name, email, password, role)
|
insert into members(name, email, password, role)
|
||||||
values ('어드민', 'a@a.a', 'a', 'ADMIN');
|
values ('어드민', 'a@a.a', 'a', 'ADMIN');
|
||||||
insert into member(name, email, password, role)
|
insert into members(name, email, password, role)
|
||||||
values ('1호', '1@1.1', '1', 'MEMBER');
|
values ('1호', '1@1.1', '1', 'MEMBER');
|
||||||
insert into member(name, email, password, role)
|
insert into members(name, email, password, role)
|
||||||
values ('2호', '2@2.2', '2', 'MEMBER');
|
values ('2호', '2@2.2', '2', 'MEMBER');
|
||||||
insert into member(name, email, password, role)
|
insert into members(name, email, password, role)
|
||||||
values ('3호', '3@3.3', '3', 'MEMBER');
|
values ('3호', '3@3.3', '3', 'MEMBER');
|
||||||
insert into member(name, email, password, role)
|
insert into members(name, email, password, role)
|
||||||
values ('4호', '4@4.4', '4', 'MEMBER');
|
values ('4호', '4@4.4', '4', 'MEMBER');
|
||||||
|
|
||||||
-- 예약: 결제 완료
|
-- 예약: 결제 완료
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (1, DATEADD('DAY', -1, CURRENT_DATE()) - 1, 1, 1, 'CONFIRMED');
|
values (1, DATEADD('DAY', -1, CURRENT_DATE()) - 1, 1, 1, 'CONFIRMED');
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (2, DATEADD('DAY', -2, CURRENT_DATE()) - 2, 3, 2, 'CONFIRMED');
|
values (2, DATEADD('DAY', -2, CURRENT_DATE()) - 2, 3, 2, 'CONFIRMED');
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (3, DATEADD('DAY', -3, CURRENT_DATE()), 2, 2, 'CONFIRMED');
|
values (3, DATEADD('DAY', -3, CURRENT_DATE()), 2, 2, 'CONFIRMED');
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (4, DATEADD('DAY', -4, CURRENT_DATE()), 1, 2, 'CONFIRMED');
|
values (4, DATEADD('DAY', -4, CURRENT_DATE()), 1, 2, 'CONFIRMED');
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (5, DATEADD('DAY', -5, CURRENT_DATE()), 1, 3, 'CONFIRMED');
|
values (5, DATEADD('DAY', -5, CURRENT_DATE()), 1, 3, 'CONFIRMED');
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (2, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'CONFIRMED');
|
values (2, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'CONFIRMED');
|
||||||
|
|
||||||
-- 예약: 결제 대기
|
-- 예약: 결제 대기
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (2, DATEADD('DAY', 8, CURRENT_DATE()), 2, 4, 'CONFIRMED_PAYMENT_REQUIRED');
|
values (2, DATEADD('DAY', 8, CURRENT_DATE()), 2, 4, 'CONFIRMED_PAYMENT_REQUIRED');
|
||||||
|
|
||||||
-- 예약 대기
|
-- 예약 대기
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (3, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING');
|
values (3, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING');
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (4, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING');
|
values (4, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING');
|
||||||
insert into reservation(member_id, date, time_id, theme_id, reservation_status)
|
insert into reservations(member_id, date, time_id, theme_id, reservation_status)
|
||||||
values (5, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING');
|
values (5, DATEADD('DAY', 7, CURRENT_DATE()), 2, 4, 'WAITING');
|
||||||
|
|
||||||
-- 결제 정보
|
-- 결제 정보
|
||||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||||
values ('orderId-1', 'paymentKey-1', 10000, 1, CURRENT_DATE);
|
values ('orderId-1', 'paymentKey-1', 10000, 1, CURRENT_DATE);
|
||||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||||
values ('orderId-2', 'paymentKey-2', 20000, 2, CURRENT_DATE);
|
values ('orderId-2', 'paymentKey-2', 20000, 2, CURRENT_DATE);
|
||||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||||
values ('orderId-3', 'paymentKey-3', 30000, 3, CURRENT_DATE);
|
values ('orderId-3', 'paymentKey-3', 30000, 3, CURRENT_DATE);
|
||||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||||
values ('orderId-4', 'paymentKey-4', 40000, 4, CURRENT_DATE);
|
values ('orderId-4', 'paymentKey-4', 40000, 4, CURRENT_DATE);
|
||||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||||
values ('orderId-5', 'paymentKey-5', 50000, 5, CURRENT_DATE);
|
values ('orderId-5', 'paymentKey-5', 50000, 5, CURRENT_DATE);
|
||||||
insert into payment(order_id, payment_key, total_amount, reservation_id, approved_at)
|
insert into payments(order_id, payment_key, total_amount, reservation_id, approved_at)
|
||||||
values ('orderId-6', 'paymentKey-6', 60000, 6, CURRENT_DATE);
|
values ('orderId-6', 'paymentKey-6', 60000, 6, CURRENT_DATE);
|
||||||
@ -97,7 +97,7 @@ function checkDateAndTheme() {
|
|||||||
|
|
||||||
function fetchAvailableTimes(date, themeId) {
|
function fetchAvailableTimes(date, themeId) {
|
||||||
|
|
||||||
fetch(`/times/filter?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint
|
fetch(`/times/search?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@ -121,12 +121,12 @@ function renderAvailableTimes(times) {
|
|||||||
timeSlots.innerHTML = '<div class="no-times">선택할 수 있는 시간이 없습니다.</div>';
|
timeSlots.innerHTML = '<div class="no-times">선택할 수 있는 시간이 없습니다.</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
times.data.reservationTimes.forEach(time => {
|
times.data.times.forEach(time => {
|
||||||
const startAt = time.startAt;
|
const startAt = time.startAt;
|
||||||
const timeId = time.timeId;
|
const timeId = time.id;
|
||||||
const alreadyBooked = time.alreadyBooked;
|
const isAvailable = time.isAvailable;
|
||||||
|
|
||||||
const div = createSlot('time', startAt, timeId, alreadyBooked); // createSlot('time', 시작 시간, time id, 예약 여부)
|
const div = createSlot('time', startAt, timeId, isAvailable); // createSlot('time', 시작 시간, time id, 예약 여부)
|
||||||
timeSlots.appendChild(div);
|
timeSlots.appendChild(div);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ function checkDateAndThemeAndTime() {
|
|||||||
const waitButton = document.getElementById("wait-button");
|
const waitButton = document.getElementById("wait-button");
|
||||||
|
|
||||||
if (selectedDate && selectedThemeElement && selectedTimeElement) {
|
if (selectedDate && selectedThemeElement && selectedTimeElement) {
|
||||||
if (selectedTimeElement.getAttribute('data-time-booked') === 'true') {
|
if (selectedTimeElement.getAttribute('data-time-booked') === 'false') {
|
||||||
// 선택된 시간이 이미 예약된 경우
|
// 선택된 시간이 이미 예약된 경우
|
||||||
reserveButton.classList.add("disabled");
|
reserveButton.classList.add("disabled");
|
||||||
waitButton.classList.remove("disabled"); // 예약 대기 버튼 활성화
|
waitButton.classList.remove("disabled"); // 예약 대기 버튼 활성화
|
||||||
|
|||||||
@ -38,7 +38,7 @@ function approve(event) {
|
|||||||
const row = event.target.closest('tr');
|
const row = event.target.closest('tr');
|
||||||
const id = row.cells[0].textContent;
|
const id = row.cells[0].textContent;
|
||||||
|
|
||||||
const endpoint = `/reservations/waiting/${id}/approve`
|
const endpoint = `/reservations/waiting/${id}/confirm`
|
||||||
return fetch(endpoint, {
|
return fetch(endpoint, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
@ -51,7 +51,7 @@ function deny(event) {
|
|||||||
const row = event.target.closest('tr');
|
const row = event.target.closest('tr');
|
||||||
const id = row.cells[0].textContent;
|
const id = row.cells[0].textContent;
|
||||||
|
|
||||||
const endpoint = `/reservations/waiting/${id}/deny`
|
const endpoint = `/reservations/waiting/${id}/reject`
|
||||||
return fetch(endpoint, {
|
return fetch(endpoint, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import roomescape.member.infrastructure.persistence.MemberRepository
|
|||||||
import roomescape.util.JwtFixture
|
import roomescape.util.JwtFixture
|
||||||
import roomescape.util.MemberFixture
|
import roomescape.util.MemberFixture
|
||||||
|
|
||||||
|
|
||||||
class AuthServiceTest : BehaviorSpec({
|
class AuthServiceTest : BehaviorSpec({
|
||||||
val memberRepository: MemberRepository = mockk()
|
val memberRepository: MemberRepository = mockk()
|
||||||
val memberService: MemberService = MemberService(memberRepository)
|
val memberService: MemberService = MemberService(memberRepository)
|
||||||
@ -43,7 +43,6 @@ class AuthControllerTest(
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = userRequest,
|
body = userRequest,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
header {
|
header {
|
||||||
@ -66,7 +65,6 @@ class AuthControllerTest(
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = userRequest,
|
body = userRequest,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isBadRequest() }
|
status { isBadRequest() }
|
||||||
jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name))
|
jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name))
|
||||||
@ -83,7 +81,6 @@ class AuthControllerTest(
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = invalidRequest,
|
body = invalidRequest,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isBadRequest() }
|
status { isBadRequest() }
|
||||||
jsonPath("$.message", containsString("이메일 형식이 일치하지 않습니다."))
|
jsonPath("$.message", containsString("이메일 형식이 일치하지 않습니다."))
|
||||||
@ -97,7 +94,6 @@ class AuthControllerTest(
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = invalidRequest,
|
body = invalidRequest,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isBadRequest() }
|
status { isBadRequest() }
|
||||||
jsonPath("$.message", containsString("비밀번호는 공백일 수 없습니다."))
|
jsonPath("$.message", containsString("비밀번호는 공백일 수 없습니다."))
|
||||||
@ -116,7 +112,6 @@ class AuthControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
jsonPath("$.data.name", equalTo(user.name))
|
jsonPath("$.data.name", equalTo(user.name))
|
||||||
@ -134,7 +129,6 @@ class AuthControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isBadRequest() }
|
status { isBadRequest() }
|
||||||
jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name))
|
jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name))
|
||||||
@ -153,7 +147,6 @@ class AuthControllerTest(
|
|||||||
runPostTest(
|
runPostTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header {
|
header {
|
||||||
@ -170,7 +163,6 @@ class AuthControllerTest(
|
|||||||
runPostTest(
|
runPostTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
header {
|
header {
|
||||||
@ -8,7 +8,7 @@ import io.mockk.every
|
|||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import jakarta.servlet.http.Cookie
|
import jakarta.servlet.http.Cookie
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import roomescape.auth.web.TokenResponse
|
import roomescape.auth.web.LoginResponse
|
||||||
|
|
||||||
class CookieUtilsTest : FunSpec({
|
class CookieUtilsTest : FunSpec({
|
||||||
context("HttpServletRequest에서 accessToken 쿠키를 가져온다.") {
|
context("HttpServletRequest에서 accessToken 쿠키를 가져온다.") {
|
||||||
@ -45,9 +45,9 @@ class CookieUtilsTest : FunSpec({
|
|||||||
}
|
}
|
||||||
|
|
||||||
context("TokenResponse를 쿠키로 반환한다.") {
|
context("TokenResponse를 쿠키로 반환한다.") {
|
||||||
val tokenResponse = TokenResponse("test-token")
|
val loginResponse = LoginResponse("test-token")
|
||||||
|
|
||||||
val result: String = tokenResponse.toResponseCookie()
|
val result: String = loginResponse.toResponseCookie()
|
||||||
|
|
||||||
result.split("; ") shouldContainAll listOf(
|
result.split("; ") shouldContainAll listOf(
|
||||||
"accessToken=test-token",
|
"accessToken=test-token",
|
||||||
@ -8,7 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired
|
|||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
import roomescape.member.web.MemberController
|
import roomescape.member.web.MemberController
|
||||||
import roomescape.member.web.MembersResponse
|
import roomescape.member.web.MemberRetrieveListResponse
|
||||||
import roomescape.util.MemberFixture
|
import roomescape.util.MemberFixture
|
||||||
import roomescape.util.RoomescapeApiTest
|
import roomescape.util.RoomescapeApiTest
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@ -35,14 +35,13 @@ class MemberControllerTest(
|
|||||||
val result: String = runGetTest(
|
val result: String = runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
}.andReturn().response.contentAsString
|
}.andReturn().response.contentAsString
|
||||||
|
|
||||||
val response: MembersResponse = readValue(
|
val response: MemberRetrieveListResponse = readValue(
|
||||||
responseJson = result,
|
responseJson = result,
|
||||||
valueType = MembersResponse::class.java
|
valueType = MemberRetrieveListResponse::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
assertSoftly(response.members) {
|
assertSoftly(response.members) {
|
||||||
@ -59,7 +58,6 @@ class MemberControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header {
|
header {
|
||||||
@ -74,7 +72,6 @@ class MemberControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header {
|
header {
|
||||||
@ -13,7 +13,7 @@ import roomescape.common.exception.ErrorType
|
|||||||
import roomescape.common.exception.RoomescapeException
|
import roomescape.common.exception.RoomescapeException
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
||||||
import roomescape.payment.web.PaymentCancel
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
import roomescape.util.PaymentFixture
|
import roomescape.util.PaymentFixture
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ class PaymentServiceTest : FunSpec({
|
|||||||
every { paymentRepository.findPaymentKeyByReservationId(reservationId) } returns null
|
every { paymentRepository.findPaymentKeyByReservationId(reservationId) } returns null
|
||||||
|
|
||||||
val exception = shouldThrow<RoomescapeException> {
|
val exception = shouldThrow<RoomescapeException> {
|
||||||
paymentService.cancelPaymentByAdmin(reservationId)
|
paymentService.createCanceledPaymentByReservationId(reservationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertSoftly(exception) {
|
assertSoftly(exception) {
|
||||||
@ -51,7 +51,7 @@ class PaymentServiceTest : FunSpec({
|
|||||||
} returns null
|
} returns null
|
||||||
|
|
||||||
val exception = shouldThrow<RoomescapeException> {
|
val exception = shouldThrow<RoomescapeException> {
|
||||||
paymentService.cancelPaymentByAdmin(reservationId)
|
paymentService.createCanceledPaymentByReservationId(reservationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertSoftly(exception) {
|
assertSoftly(exception) {
|
||||||
@ -79,7 +79,7 @@ class PaymentServiceTest : FunSpec({
|
|||||||
cancelAmount = paymentEntity.totalAmount,
|
cancelAmount = paymentEntity.totalAmount,
|
||||||
)
|
)
|
||||||
|
|
||||||
val result: PaymentCancel.Request = paymentService.cancelPaymentByAdmin(reservationId)
|
val result: PaymentCancelRequest = paymentService.createCanceledPaymentByReservationId(reservationId)
|
||||||
|
|
||||||
assertSoftly(result) {
|
assertSoftly(result) {
|
||||||
this.paymentKey shouldBe paymentKey
|
this.paymentKey shouldBe paymentKey
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package roomescape.payment.web.support
|
package roomescape.payment.infrastructure.client
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||||
@ -6,24 +6,22 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|||||||
import io.kotest.assertions.assertSoftly
|
import io.kotest.assertions.assertSoftly
|
||||||
import io.kotest.core.spec.style.StringSpec
|
import io.kotest.core.spec.style.StringSpec
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import roomescape.payment.SampleTossPaymentConst
|
import roomescape.payment.web.PaymentCancelResponse
|
||||||
import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer
|
|
||||||
import roomescape.payment.web.PaymentCancel
|
|
||||||
|
|
||||||
class PaymentCancelResponseDeserializerTest : StringSpec({
|
class PaymentCancelResponseDeserializerTest : StringSpec({
|
||||||
|
|
||||||
val objectMapper: ObjectMapper = jacksonObjectMapper().registerModule(
|
val objectMapper: ObjectMapper = jacksonObjectMapper().registerModule(
|
||||||
SimpleModule().addDeserializer(
|
SimpleModule().addDeserializer(
|
||||||
PaymentCancel.Response::class.java,
|
PaymentCancelResponse::class.java,
|
||||||
PaymentCancelResponseDeserializer()
|
PaymentCancelResponseDeserializer()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
"결제 취소 응답을 역직렬화하여 PaymentCancelResponse 객체를 생성한다" {
|
"결제 취소 응답을 역직렬화하여 PaymentCancelResponse 객체를 생성한다" {
|
||||||
val cancelResponseJson: String = SampleTossPaymentConst.cancelJson
|
val cancelResponseJson: String = SampleTossPaymentConst.cancelJson
|
||||||
val cancelResponse: PaymentCancel.Response = objectMapper.readValue(
|
val cancelResponse: PaymentCancelResponse = objectMapper.readValue(
|
||||||
cancelResponseJson,
|
cancelResponseJson,
|
||||||
PaymentCancel.Response::class.java
|
PaymentCancelResponse::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
assertSoftly(cancelResponse) {
|
assertSoftly(cancelResponse) {
|
||||||
@ -1,35 +1,26 @@
|
|||||||
package roomescape.payment
|
package roomescape.payment.infrastructure.client
|
||||||
|
|
||||||
import roomescape.payment.SampleTossPaymentConst.amount
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
import roomescape.payment.web.PaymentApprove
|
|
||||||
import roomescape.payment.web.PaymentCancel
|
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
object SampleTossPaymentConst {
|
object SampleTossPaymentConst {
|
||||||
@JvmField
|
|
||||||
val paymentKey: String = "5EnNZRJGvaBX7zk2yd8ydw26XvwXkLrx9POLqKQjmAw4b0e1"
|
val paymentKey: String = "5EnNZRJGvaBX7zk2yd8ydw26XvwXkLrx9POLqKQjmAw4b0e1"
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val orderId: String = "MC4wODU4ODQwMzg4NDk0"
|
val orderId: String = "MC4wODU4ODQwMzg4NDk0"
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val amount: Long = 1000L
|
val amount: Long = 1000L
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val paymentType: String = "카드"
|
val paymentType: String = "카드"
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val cancelReason: String = "테스트 결제 취소"
|
val cancelReason: String = "테스트 결제 취소"
|
||||||
|
|
||||||
@JvmField
|
val paymentRequest: PaymentApproveRequest = PaymentApproveRequest(
|
||||||
val paymentRequest: PaymentApprove.Request = PaymentApprove.Request(
|
|
||||||
paymentKey,
|
paymentKey,
|
||||||
orderId,
|
orderId,
|
||||||
amount,
|
amount,
|
||||||
paymentType
|
paymentType
|
||||||
)
|
)
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val paymentRequestJson: String = """
|
val paymentRequestJson: String = """
|
||||||
{
|
{
|
||||||
"paymentKey": "$paymentKey",
|
"paymentKey": "$paymentKey",
|
||||||
@ -39,21 +30,18 @@ object SampleTossPaymentConst {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
@JvmField
|
val cancelRequest: PaymentCancelRequest = PaymentCancelRequest(
|
||||||
val cancelRequest: PaymentCancel.Request = PaymentCancel.Request(
|
|
||||||
paymentKey,
|
paymentKey,
|
||||||
amount,
|
amount,
|
||||||
cancelReason
|
cancelReason
|
||||||
)
|
)
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val cancelRequestJson: String = """
|
val cancelRequestJson: String = """
|
||||||
{
|
{
|
||||||
"cancelReason": "$cancelReason"
|
"cancelReason": "$cancelReason"
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val tossPaymentErrorJson: String = """
|
val tossPaymentErrorJson: String = """
|
||||||
{
|
{
|
||||||
"code": "ERROR_CODE",
|
"code": "ERROR_CODE",
|
||||||
@ -61,7 +49,6 @@ object SampleTossPaymentConst {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val confirmJson: String = """
|
val confirmJson: String = """
|
||||||
{
|
{
|
||||||
"mId": "tosspayments",
|
"mId": "tosspayments",
|
||||||
@ -127,7 +114,6 @@ object SampleTossPaymentConst {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val cancelJson: String = """
|
val cancelJson: String = """
|
||||||
{
|
{
|
||||||
"mId": "tosspayments",
|
"mId": "tosspayments",
|
||||||
@ -206,7 +192,3 @@ object SampleTossPaymentConst {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main() {
|
|
||||||
println((amount / 1.1).roundToLong())
|
|
||||||
}
|
|
||||||
@ -16,9 +16,8 @@ import org.springframework.test.web.client.response.MockRestResponseCreators.wit
|
|||||||
import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess
|
import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess
|
||||||
import roomescape.common.exception.ErrorType
|
import roomescape.common.exception.ErrorType
|
||||||
import roomescape.common.exception.RoomescapeException
|
import roomescape.common.exception.RoomescapeException
|
||||||
import roomescape.payment.SampleTossPaymentConst
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
import roomescape.payment.web.PaymentApprove
|
import roomescape.payment.web.PaymentCancelResponse
|
||||||
import roomescape.payment.web.PaymentCancel
|
|
||||||
|
|
||||||
@RestClientTest(TossPaymentClient::class)
|
@RestClientTest(TossPaymentClient::class)
|
||||||
class TossPaymentClientTest(
|
class TossPaymentClientTest(
|
||||||
@ -48,7 +47,7 @@ class TossPaymentClientTest(
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
val paymentRequest = SampleTossPaymentConst.paymentRequest
|
val paymentRequest = SampleTossPaymentConst.paymentRequest
|
||||||
val paymentResponse: PaymentApprove.Response = client.confirmPayment(paymentRequest)
|
val paymentResponse: PaymentApproveResponse = client.confirm(paymentRequest)
|
||||||
|
|
||||||
assertSoftly(paymentResponse) {
|
assertSoftly(paymentResponse) {
|
||||||
this.paymentKey shouldBe paymentRequest.paymentKey
|
this.paymentKey shouldBe paymentRequest.paymentKey
|
||||||
@ -70,7 +69,7 @@ class TossPaymentClientTest(
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
val exception = shouldThrow<RoomescapeException> {
|
val exception = shouldThrow<RoomescapeException> {
|
||||||
client.confirmPayment(paymentRequest)
|
client.confirm(paymentRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertSoftly(exception) {
|
assertSoftly(exception) {
|
||||||
@ -102,8 +101,8 @@ class TossPaymentClientTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val cancelRequest: PaymentCancel.Request = SampleTossPaymentConst.cancelRequest
|
val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest
|
||||||
val cancelResponse: PaymentCancel.Response = client.cancelPayment(cancelRequest)
|
val cancelResponse: PaymentCancelResponse = client.cancel(cancelRequest)
|
||||||
|
|
||||||
assertSoftly(cancelResponse) {
|
assertSoftly(cancelResponse) {
|
||||||
this.cancelStatus shouldBe "DONE"
|
this.cancelStatus shouldBe "DONE"
|
||||||
@ -121,11 +120,11 @@ class TossPaymentClientTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val cancelRequest: PaymentCancel.Request = SampleTossPaymentConst.cancelRequest
|
val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest
|
||||||
|
|
||||||
// then
|
// then
|
||||||
val exception = shouldThrow<RoomescapeException> {
|
val exception = shouldThrow<RoomescapeException> {
|
||||||
client.cancelPayment(cancelRequest)
|
client.cancel(cancelRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertSoftly(exception) {
|
assertSoftly(exception) {
|
||||||
@ -94,7 +94,7 @@ class PaymentRepositoryTest(
|
|||||||
return ReservationFixture.create().also {
|
return ReservationFixture.create().also {
|
||||||
entityManager.persist(it.member)
|
entityManager.persist(it.member)
|
||||||
entityManager.persist(it.theme)
|
entityManager.persist(it.theme)
|
||||||
entityManager.persist(it.reservationTime)
|
entityManager.persist(it.time)
|
||||||
entityManager.persist(it)
|
entityManager.persist(it)
|
||||||
|
|
||||||
entityManager.flush()
|
entityManager.flush()
|
||||||
@ -13,19 +13,19 @@ import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
|||||||
import roomescape.theme.business.ThemeService
|
import roomescape.theme.business.ThemeService
|
||||||
import roomescape.util.MemberFixture
|
import roomescape.util.MemberFixture
|
||||||
import roomescape.util.ReservationFixture
|
import roomescape.util.ReservationFixture
|
||||||
import roomescape.util.ReservationTimeFixture
|
import roomescape.util.TimeFixture
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
class ReservationServiteTest : FunSpec({
|
class ReservationServiceTest : FunSpec({
|
||||||
|
|
||||||
val reservationRepository: ReservationRepository = mockk()
|
val reservationRepository: ReservationRepository = mockk()
|
||||||
val reservationTimeService: ReservationTimeService = mockk()
|
val timeService: TimeService = mockk()
|
||||||
val memberService: MemberService = mockk()
|
val memberService: MemberService = mockk()
|
||||||
val themeService: ThemeService = mockk()
|
val themeService: ThemeService = mockk()
|
||||||
val reservationService = ReservationService(
|
val reservationService = ReservationService(
|
||||||
reservationRepository,
|
reservationRepository,
|
||||||
reservationTimeService,
|
timeService,
|
||||||
memberService,
|
memberService,
|
||||||
themeService
|
themeService
|
||||||
)
|
)
|
||||||
@ -51,7 +51,7 @@ class ReservationServiteTest : FunSpec({
|
|||||||
} returns false
|
} returns false
|
||||||
|
|
||||||
every {
|
every {
|
||||||
themeService.findThemeById(any())
|
themeService.findById(any())
|
||||||
} returns mockk()
|
} returns mockk()
|
||||||
|
|
||||||
every {
|
every {
|
||||||
@ -65,8 +65,8 @@ class ReservationServiteTest : FunSpec({
|
|||||||
)
|
)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
reservationTimeService.findTimeById(any())
|
timeService.findById(any())
|
||||||
} returns ReservationTimeFixture.create()
|
} returns TimeFixture.create()
|
||||||
|
|
||||||
shouldThrow<RoomescapeException> {
|
shouldThrow<RoomescapeException> {
|
||||||
reservationService.addReservation(reservationRequest, 1L)
|
reservationService.addReservation(reservationRequest, 1L)
|
||||||
@ -81,8 +81,8 @@ class ReservationServiteTest : FunSpec({
|
|||||||
)
|
)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
reservationTimeService.findTimeById(reservationRequest.timeId)
|
timeService.findById(reservationRequest.timeId)
|
||||||
} returns ReservationTimeFixture.create(
|
} returns TimeFixture.create(
|
||||||
startAt = LocalTime.now().minusMinutes(1)
|
startAt = LocalTime.now().minusMinutes(1)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ class ReservationServiteTest : FunSpec({
|
|||||||
themeId = reservationRequest.themeId,
|
themeId = reservationRequest.themeId,
|
||||||
timeId = reservationRequest.timeId
|
timeId = reservationRequest.timeId
|
||||||
)
|
)
|
||||||
reservationService.addWaiting(waitingRequest, 1L)
|
reservationService.createWaiting(waitingRequest, 1L)
|
||||||
}.also {
|
}.also {
|
||||||
it.errorType shouldBe ErrorType.HAS_RESERVATION_OR_WAITING
|
it.errorType shouldBe ErrorType.HAS_RESERVATION_OR_WAITING
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ class ReservationServiteTest : FunSpec({
|
|||||||
val endAt = startFrom.minusDays(1)
|
val endAt = startFrom.minusDays(1)
|
||||||
|
|
||||||
shouldThrow<RoomescapeException> {
|
shouldThrow<RoomescapeException> {
|
||||||
reservationService.findFilteredReservations(
|
reservationService.searchReservations(
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
startFrom,
|
startFrom,
|
||||||
@ -147,7 +147,7 @@ class ReservationServiteTest : FunSpec({
|
|||||||
} returns member
|
} returns member
|
||||||
|
|
||||||
shouldThrow<RoomescapeException> {
|
shouldThrow<RoomescapeException> {
|
||||||
reservationService.approveWaiting(1L, member.id!!)
|
reservationService.confirmWaiting(1L, member.id!!)
|
||||||
}.also {
|
}.also {
|
||||||
it.errorType shouldBe ErrorType.PERMISSION_DOES_NOT_EXIST
|
it.errorType shouldBe ErrorType.PERMISSION_DOES_NOT_EXIST
|
||||||
}
|
}
|
||||||
@ -166,7 +166,7 @@ class ReservationServiteTest : FunSpec({
|
|||||||
} returns true
|
} returns true
|
||||||
|
|
||||||
shouldThrow<RoomescapeException> {
|
shouldThrow<RoomescapeException> {
|
||||||
reservationService.approveWaiting(reservationId, member.id!!)
|
reservationService.confirmWaiting(reservationId, member.id!!)
|
||||||
}.also {
|
}.also {
|
||||||
it.errorType shouldBe ErrorType.RESERVATION_DUPLICATED
|
it.errorType shouldBe ErrorType.RESERVATION_DUPLICATED
|
||||||
}
|
}
|
||||||
@ -9,12 +9,12 @@ import io.mockk.just
|
|||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import roomescape.payment.business.PaymentService
|
import roomescape.payment.business.PaymentService
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||||
import roomescape.payment.web.PaymentCancel
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
import roomescape.payment.web.toReservationPaymentResponse
|
import roomescape.payment.web.toCreateResponse
|
||||||
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.web.ReservationRequest
|
import roomescape.reservation.web.ReservationCreateWithPaymentRequest
|
||||||
import roomescape.reservation.web.ReservationResponse
|
import roomescape.reservation.web.ReservationRetrieveResponse
|
||||||
import roomescape.util.*
|
import roomescape.util.*
|
||||||
|
|
||||||
class ReservationWithPaymentServiceTest : FunSpec({
|
class ReservationWithPaymentServiceTest : FunSpec({
|
||||||
@ -26,37 +26,37 @@ class ReservationWithPaymentServiceTest : FunSpec({
|
|||||||
paymentService = paymentService
|
paymentService = paymentService
|
||||||
)
|
)
|
||||||
|
|
||||||
val reservationRequest: ReservationRequest = ReservationFixture.createRequest()
|
val reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest = ReservationFixture.createRequest()
|
||||||
val paymentApproveResponse = PaymentFixture.createApproveResponse()
|
val paymentApproveResponse = PaymentFixture.createApproveResponse()
|
||||||
val memberId = 1L
|
val memberId = 1L
|
||||||
val reservationEntity: ReservationEntity = ReservationFixture.create(
|
val reservationEntity: ReservationEntity = ReservationFixture.create(
|
||||||
id = 1L,
|
id = 1L,
|
||||||
date = reservationRequest.date,
|
date = reservationCreateWithPaymentRequest.date,
|
||||||
reservationTime = ReservationTimeFixture.create(id = reservationRequest.timeId),
|
time = TimeFixture.create(id = reservationCreateWithPaymentRequest.timeId),
|
||||||
theme = ThemeFixture.create(id = reservationRequest.themeId),
|
theme = ThemeFixture.create(id = reservationCreateWithPaymentRequest.themeId),
|
||||||
member = MemberFixture.create(id = memberId),
|
member = MemberFixture.create(id = memberId),
|
||||||
status = ReservationStatus.CONFIRMED
|
status = ReservationStatus.CONFIRMED
|
||||||
)
|
)
|
||||||
val paymentEntity: PaymentEntity = PaymentFixture.create(
|
val paymentEntity: PaymentEntity = PaymentFixture.create(
|
||||||
id = 1L,
|
id = 1L,
|
||||||
orderId = reservationRequest.orderId,
|
orderId = reservationCreateWithPaymentRequest.orderId,
|
||||||
paymentKey = reservationRequest.paymentKey,
|
paymentKey = reservationCreateWithPaymentRequest.paymentKey,
|
||||||
totalAmount = reservationRequest.amount,
|
totalAmount = reservationCreateWithPaymentRequest.amount,
|
||||||
reservation = reservationEntity,
|
reservation = reservationEntity,
|
||||||
)
|
)
|
||||||
|
|
||||||
context("addReservationWithPayment") {
|
context("addReservationWithPayment") {
|
||||||
test("예약 및 결제 정보를 저장한다.") {
|
test("예약 및 결제 정보를 저장한다.") {
|
||||||
every {
|
every {
|
||||||
reservationService.addReservation(reservationRequest, memberId)
|
reservationService.addReservation(reservationCreateWithPaymentRequest, memberId)
|
||||||
} returns reservationEntity
|
} returns reservationEntity
|
||||||
|
|
||||||
every {
|
every {
|
||||||
paymentService.savePayment(paymentApproveResponse, reservationEntity)
|
paymentService.createPayment(paymentApproveResponse, reservationEntity)
|
||||||
} returns paymentEntity.toReservationPaymentResponse()
|
} returns paymentEntity.toCreateResponse()
|
||||||
|
|
||||||
val result: ReservationResponse = reservationWithPaymentService.addReservationWithPayment(
|
val result: ReservationRetrieveResponse = reservationWithPaymentService.createReservationAndPayment(
|
||||||
request = reservationRequest,
|
request = reservationCreateWithPaymentRequest,
|
||||||
paymentInfo = paymentApproveResponse,
|
paymentInfo = paymentApproveResponse,
|
||||||
memberId = memberId
|
memberId = memberId
|
||||||
)
|
)
|
||||||
@ -65,7 +65,7 @@ class ReservationWithPaymentServiceTest : FunSpec({
|
|||||||
this.id shouldBe reservationEntity.id
|
this.id shouldBe reservationEntity.id
|
||||||
this.date shouldBe reservationEntity.date
|
this.date shouldBe reservationEntity.date
|
||||||
this.member.id shouldBe reservationEntity.member.id
|
this.member.id shouldBe reservationEntity.member.id
|
||||||
this.time.id shouldBe reservationEntity.reservationTime.id
|
this.time.id shouldBe reservationEntity.time.id
|
||||||
this.theme.id shouldBe reservationEntity.theme.id
|
this.theme.id shouldBe reservationEntity.theme.id
|
||||||
this.status shouldBe ReservationStatus.CONFIRMED
|
this.status shouldBe ReservationStatus.CONFIRMED
|
||||||
}
|
}
|
||||||
@ -74,21 +74,21 @@ class ReservationWithPaymentServiceTest : FunSpec({
|
|||||||
|
|
||||||
context("removeReservationWithPayment") {
|
context("removeReservationWithPayment") {
|
||||||
test("예약 및 결제 정보를 삭제하고, 결제 취소 정보를 저장한다.") {
|
test("예약 및 결제 정보를 삭제하고, 결제 취소 정보를 저장한다.") {
|
||||||
val paymentCancelRequest: PaymentCancel.Request = PaymentFixture.createCancelRequest().copy(
|
val paymentCancelRequest: PaymentCancelRequest = PaymentFixture.createCancelRequest().copy(
|
||||||
paymentKey = paymentEntity.paymentKey,
|
paymentKey = paymentEntity.paymentKey,
|
||||||
amount = paymentEntity.totalAmount,
|
amount = paymentEntity.totalAmount,
|
||||||
cancelReason = "고객 요청"
|
cancelReason = "고객 요청"
|
||||||
)
|
)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
paymentService.cancelPaymentByAdmin(reservationEntity.id!!)
|
paymentService.createCanceledPaymentByReservationId(reservationEntity.id!!)
|
||||||
} returns paymentCancelRequest
|
} returns paymentCancelRequest
|
||||||
|
|
||||||
every {
|
every {
|
||||||
reservationService.removeReservationById(reservationEntity.id!!, reservationEntity.member.id!!)
|
reservationService.deleteReservation(reservationEntity.id!!, reservationEntity.member.id!!)
|
||||||
} just Runs
|
} just Runs
|
||||||
|
|
||||||
val result: PaymentCancel.Request = reservationWithPaymentService.removeReservationWithPayment(
|
val result: PaymentCancelRequest = reservationWithPaymentService.deleteReservationAndPayment(
|
||||||
reservationId = reservationEntity.id!!,
|
reservationId = reservationEntity.id!!,
|
||||||
memberId = reservationEntity.member.id!!
|
memberId = reservationEntity.member.id!!
|
||||||
)
|
)
|
||||||
@ -10,17 +10,17 @@ import org.springframework.http.HttpStatus
|
|||||||
import roomescape.common.exception.ErrorType
|
import roomescape.common.exception.ErrorType
|
||||||
import roomescape.common.exception.RoomescapeException
|
import roomescape.common.exception.RoomescapeException
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
|
import roomescape.reservation.infrastructure.persistence.TimeRepository
|
||||||
import roomescape.reservation.web.ReservationTimeRequest
|
import roomescape.reservation.web.TimeCreateRequest
|
||||||
import roomescape.util.ReservationTimeFixture
|
import roomescape.util.TimeFixture
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
class ReservationTimeServiceTest : FunSpec({
|
class TimeServiceTest : FunSpec({
|
||||||
val reservationTimeRepository: ReservationTimeRepository = mockk()
|
val timeRepository: TimeRepository = mockk()
|
||||||
val reservationRepository: ReservationRepository = mockk()
|
val reservationRepository: ReservationRepository = mockk()
|
||||||
|
|
||||||
val reservationTimeService = ReservationTimeService(
|
val timeService = TimeService(
|
||||||
reservationTimeRepository = reservationTimeRepository,
|
timeRepository = timeRepository,
|
||||||
reservationRepository = reservationRepository
|
reservationRepository = reservationRepository
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,13 +28,12 @@ class ReservationTimeServiceTest : FunSpec({
|
|||||||
test("시간을 찾을 수 없으면 400 에러를 던진다.") {
|
test("시간을 찾을 수 없으면 400 에러를 던진다.") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
|
|
||||||
// Mocking the behavior of reservationTimeRepository.findByIdOrNull
|
every { timeRepository.findByIdOrNull(id) } returns null
|
||||||
every { reservationTimeRepository.findByIdOrNull(id) } returns null
|
|
||||||
|
|
||||||
shouldThrow<RoomescapeException> {
|
shouldThrow<RoomescapeException> {
|
||||||
reservationTimeService.findTimeById(id)
|
timeService.findById(id)
|
||||||
}.apply {
|
}.apply {
|
||||||
errorType shouldBe ErrorType.RESERVATION_TIME_NOT_FOUND
|
errorType shouldBe ErrorType.TIME_NOT_FOUND
|
||||||
httpStatus shouldBe HttpStatus.BAD_REQUEST
|
httpStatus shouldBe HttpStatus.BAD_REQUEST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,13 +41,12 @@ class ReservationTimeServiceTest : FunSpec({
|
|||||||
|
|
||||||
context("addTime") {
|
context("addTime") {
|
||||||
test("중복된 시간이 있으면 409 에러를 던진다.") {
|
test("중복된 시간이 있으면 409 에러를 던진다.") {
|
||||||
val request = ReservationTimeRequest(startAt = LocalTime.of(10, 0))
|
val request = TimeCreateRequest(startAt = LocalTime.of(10, 0))
|
||||||
|
|
||||||
// Mocking the behavior of reservationTimeRepository.findByStartAt
|
every { timeRepository.existsByStartAt(request.startAt) } returns true
|
||||||
every { reservationTimeRepository.existsByStartAt(request.startAt) } returns true
|
|
||||||
|
|
||||||
shouldThrow<RoomescapeException> {
|
shouldThrow<RoomescapeException> {
|
||||||
reservationTimeService.addTime(request)
|
timeService.createTime(request)
|
||||||
}.apply {
|
}.apply {
|
||||||
errorType shouldBe ErrorType.TIME_DUPLICATED
|
errorType shouldBe ErrorType.TIME_DUPLICATED
|
||||||
httpStatus shouldBe HttpStatus.CONFLICT
|
httpStatus shouldBe HttpStatus.CONFLICT
|
||||||
@ -60,29 +58,26 @@ class ReservationTimeServiceTest : FunSpec({
|
|||||||
test("시간을 찾을 수 없으면 400 에러를 던진다.") {
|
test("시간을 찾을 수 없으면 400 에러를 던진다.") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
|
|
||||||
// Mocking the behavior of reservationTimeRepository.findByIdOrNull
|
every { timeRepository.findByIdOrNull(id) } returns null
|
||||||
every { reservationTimeRepository.findByIdOrNull(id) } returns null
|
|
||||||
|
|
||||||
shouldThrow<RoomescapeException> {
|
shouldThrow<RoomescapeException> {
|
||||||
reservationTimeService.removeTimeById(id)
|
timeService.deleteTime(id)
|
||||||
}.apply {
|
}.apply {
|
||||||
errorType shouldBe ErrorType.RESERVATION_TIME_NOT_FOUND
|
errorType shouldBe ErrorType.TIME_NOT_FOUND
|
||||||
httpStatus shouldBe HttpStatus.BAD_REQUEST
|
httpStatus shouldBe HttpStatus.BAD_REQUEST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("예약이 있는 시간이면 409 에러를 던진다.") {
|
test("예약이 있는 시간이면 409 에러를 던진다.") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
val reservationTime = ReservationTimeFixture.create()
|
val time = TimeFixture.create()
|
||||||
|
|
||||||
// Mocking the behavior of reservationTimeRepository.findByIdOrNull
|
every { timeRepository.findByIdOrNull(id) } returns time
|
||||||
every { reservationTimeRepository.findByIdOrNull(id) } returns reservationTime
|
|
||||||
|
|
||||||
// Mocking the behavior of reservationRepository.findByReservationTime
|
every { reservationRepository.findByTime(time) } returns listOf(mockk())
|
||||||
every { reservationRepository.findByReservationTime(reservationTime) } returns listOf(mockk())
|
|
||||||
|
|
||||||
shouldThrow<RoomescapeException> {
|
shouldThrow<RoomescapeException> {
|
||||||
reservationTimeService.removeTimeById(id)
|
timeService.deleteTime(id)
|
||||||
}.apply {
|
}.apply {
|
||||||
errorType shouldBe ErrorType.TIME_IS_USED_CONFLICT
|
errorType shouldBe ErrorType.TIME_IS_USED_CONFLICT
|
||||||
httpStatus shouldBe HttpStatus.CONFLICT
|
httpStatus shouldBe HttpStatus.CONFLICT
|
||||||
@ -8,12 +8,12 @@ import jakarta.persistence.EntityManager
|
|||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||||
import roomescape.reservation.web.MyReservationResponse
|
import roomescape.reservation.web.MyReservationRetrieveResponse
|
||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import roomescape.util.PaymentFixture
|
import roomescape.util.PaymentFixture
|
||||||
import roomescape.util.ReservationFixture
|
import roomescape.util.ReservationFixture
|
||||||
import roomescape.util.ReservationTimeFixture
|
|
||||||
import roomescape.util.ThemeFixture
|
import roomescape.util.ThemeFixture
|
||||||
|
import roomescape.util.TimeFixture
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
class ReservationRepositoryTest(
|
class ReservationRepositoryTest(
|
||||||
@ -21,13 +21,13 @@ class ReservationRepositoryTest(
|
|||||||
val reservationRepository: ReservationRepository,
|
val reservationRepository: ReservationRepository,
|
||||||
) : FunSpec() {
|
) : FunSpec() {
|
||||||
init {
|
init {
|
||||||
context("findByReservationTime") {
|
context("findByTime") {
|
||||||
val time = ReservationTimeFixture.create()
|
val time = TimeFixture.create()
|
||||||
|
|
||||||
beforeTest {
|
beforeTest {
|
||||||
listOf(
|
listOf(
|
||||||
ReservationFixture.create(reservationTime = time),
|
ReservationFixture.create(time = time),
|
||||||
ReservationFixture.create(reservationTime = ReservationTimeFixture.create(
|
ReservationFixture.create(time = TimeFixture.create(
|
||||||
startAt = time.startAt.plusSeconds(1)
|
startAt = time.startAt.plusSeconds(1)
|
||||||
))
|
))
|
||||||
).forEach {
|
).forEach {
|
||||||
@ -39,9 +39,9 @@ class ReservationRepositoryTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("입력된 시간과 일치하는 예약을 반환한다.") {
|
test("입력된 시간과 일치하는 예약을 반환한다.") {
|
||||||
assertSoftly(reservationRepository.findByReservationTime(time)) {
|
assertSoftly(reservationRepository.findByTime(time)) {
|
||||||
it shouldHaveSize 1
|
it shouldHaveSize 1
|
||||||
assertSoftly(it.first().reservationTime.startAt) { result ->
|
assertSoftly(it.first().time.startAt) { result ->
|
||||||
result.hour shouldBe time.startAt.hour
|
result.hour shouldBe time.startAt.hour
|
||||||
result.minute shouldBe time.startAt.minute
|
result.minute shouldBe time.startAt.minute
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ class ReservationRepositoryTest(
|
|||||||
ReservationFixture.create(date = date.plusDays(1), theme = theme1),
|
ReservationFixture.create(date = date.plusDays(1), theme = theme1),
|
||||||
ReservationFixture.create(date = date, theme = theme2),
|
ReservationFixture.create(date = date, theme = theme2),
|
||||||
).forEach {
|
).forEach {
|
||||||
entityManager.persist(it.reservationTime)
|
entityManager.persist(it.time)
|
||||||
entityManager.persist(it.member)
|
entityManager.persist(it.member)
|
||||||
entityManager.persist(it)
|
entityManager.persist(it)
|
||||||
}
|
}
|
||||||
@ -168,7 +168,7 @@ class ReservationRepositoryTest(
|
|||||||
entityManager.clear()
|
entityManager.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: List<MyReservationResponse> = reservationRepository.findMyReservations(reservation.member.id!!)
|
val result: List<MyReservationRetrieveResponse> = reservationRepository.findAllById(reservation.member.id!!)
|
||||||
|
|
||||||
result shouldHaveSize 1
|
result shouldHaveSize 1
|
||||||
assertSoftly(result.first()) {
|
assertSoftly(result.first()) {
|
||||||
@ -179,7 +179,7 @@ class ReservationRepositoryTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("결제 정보가 없다면 paymentKey와 amount는 null로 반환한다.") {
|
test("결제 정보가 없다면 paymentKey와 amount는 null로 반환한다.") {
|
||||||
val result: List<MyReservationResponse> = reservationRepository.findMyReservations(reservation.member.id!!)
|
val result: List<MyReservationRetrieveResponse> = reservationRepository.findAllById(reservation.member.id!!)
|
||||||
|
|
||||||
result shouldHaveSize 1
|
result shouldHaveSize 1
|
||||||
assertSoftly(result.first()) {
|
assertSoftly(result.first()) {
|
||||||
@ -192,7 +192,7 @@ class ReservationRepositoryTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun persistReservation(reservation: ReservationEntity) {
|
fun persistReservation(reservation: ReservationEntity) {
|
||||||
entityManager.persist(reservation.reservationTime)
|
entityManager.persist(reservation.time)
|
||||||
entityManager.persist(reservation.theme)
|
entityManager.persist(reservation.theme)
|
||||||
entityManager.persist(reservation.member)
|
entityManager.persist(reservation.member)
|
||||||
entityManager.persist(reservation)
|
entityManager.persist(reservation)
|
||||||
@ -10,8 +10,8 @@ import roomescape.member.infrastructure.persistence.MemberEntity
|
|||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import roomescape.util.MemberFixture
|
import roomescape.util.MemberFixture
|
||||||
import roomescape.util.ReservationFixture
|
import roomescape.util.ReservationFixture
|
||||||
import roomescape.util.ReservationTimeFixture
|
|
||||||
import roomescape.util.ThemeFixture
|
import roomescape.util.ThemeFixture
|
||||||
|
import roomescape.util.TimeFixture
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@ -25,7 +25,7 @@ class ReservationSearchSpecificationTest(
|
|||||||
lateinit var confirmedNotPaidYesterday: ReservationEntity
|
lateinit var confirmedNotPaidYesterday: ReservationEntity
|
||||||
lateinit var waitingTomorrow: ReservationEntity
|
lateinit var waitingTomorrow: ReservationEntity
|
||||||
lateinit var member: MemberEntity
|
lateinit var member: MemberEntity
|
||||||
lateinit var reservationTime: ReservationTimeEntity
|
lateinit var time: TimeEntity
|
||||||
lateinit var theme: ThemeEntity
|
lateinit var theme: ThemeEntity
|
||||||
|
|
||||||
"동일한 테마의 예약을 조회한다" {
|
"동일한 테마의 예약을 조회한다" {
|
||||||
@ -56,7 +56,7 @@ class ReservationSearchSpecificationTest(
|
|||||||
|
|
||||||
"동일한 예약 시간의 예약을 조회한다" {
|
"동일한 예약 시간의 예약을 조회한다" {
|
||||||
val spec = ReservationSearchSpecification()
|
val spec = ReservationSearchSpecification()
|
||||||
.sameTimeId(reservationTime.id)
|
.sameTimeId(time.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val results: List<ReservationEntity> = reservationRepository.findAll(spec)
|
val results: List<ReservationEntity> = reservationRepository.findAll(spec)
|
||||||
@ -136,7 +136,7 @@ class ReservationSearchSpecificationTest(
|
|||||||
member = MemberFixture.create().also {
|
member = MemberFixture.create().also {
|
||||||
entityManager.persist(it)
|
entityManager.persist(it)
|
||||||
}
|
}
|
||||||
reservationTime = ReservationTimeFixture.create().also {
|
time = TimeFixture.create().also {
|
||||||
entityManager.persist(it)
|
entityManager.persist(it)
|
||||||
}
|
}
|
||||||
theme = ThemeFixture.create().also {
|
theme = ThemeFixture.create().also {
|
||||||
@ -144,7 +144,7 @@ class ReservationSearchSpecificationTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
confirmedNow = ReservationFixture.create(
|
confirmedNow = ReservationFixture.create(
|
||||||
reservationTime = reservationTime,
|
time = time,
|
||||||
member = member,
|
member = member,
|
||||||
theme = theme,
|
theme = theme,
|
||||||
date = LocalDate.now(),
|
date = LocalDate.now(),
|
||||||
@ -154,7 +154,7 @@ class ReservationSearchSpecificationTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
confirmedNotPaidYesterday = ReservationFixture.create(
|
confirmedNotPaidYesterday = ReservationFixture.create(
|
||||||
reservationTime = reservationTime,
|
time = time,
|
||||||
member = member,
|
member = member,
|
||||||
theme = theme,
|
theme = theme,
|
||||||
date = LocalDate.now().minusDays(1),
|
date = LocalDate.now().minusDays(1),
|
||||||
@ -164,7 +164,7 @@ class ReservationSearchSpecificationTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
waitingTomorrow = ReservationFixture.create(
|
waitingTomorrow = ReservationFixture.create(
|
||||||
reservationTime = reservationTime,
|
time = time,
|
||||||
member = member,
|
member = member,
|
||||||
theme = theme,
|
theme = theme,
|
||||||
date = LocalDate.now().plusDays(1),
|
date = LocalDate.now().plusDays(1),
|
||||||
@ -4,30 +4,30 @@ import io.kotest.core.spec.style.FunSpec
|
|||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import jakarta.persistence.EntityManager
|
import jakarta.persistence.EntityManager
|
||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||||
import roomescape.util.ReservationTimeFixture
|
import roomescape.util.TimeFixture
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
class ReservationTimeRepositoryTest(
|
class TimeRepositoryTest(
|
||||||
val entityManager: EntityManager,
|
val entityManager: EntityManager,
|
||||||
val reservationTimeRepository: ReservationTimeRepository,
|
val timeRepository: TimeRepository,
|
||||||
) : FunSpec({
|
) : FunSpec({
|
||||||
|
|
||||||
context("existsByStartAt") {
|
context("existsByStartAt") {
|
||||||
val startAt = LocalTime.of(10, 0)
|
val startAt = LocalTime.of(10, 0)
|
||||||
|
|
||||||
beforeTest {
|
beforeTest {
|
||||||
entityManager.persist(ReservationTimeFixture.create(startAt = startAt))
|
entityManager.persist(TimeFixture.create(startAt = startAt))
|
||||||
entityManager.flush()
|
entityManager.flush()
|
||||||
entityManager.clear()
|
entityManager.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
test("동일한 시간이 있으면 true 반환") {
|
test("동일한 시간이 있으면 true 반환") {
|
||||||
reservationTimeRepository.existsByStartAt(startAt) shouldBe true
|
timeRepository.existsByStartAt(startAt) shouldBe true
|
||||||
}
|
}
|
||||||
|
|
||||||
test("동일한 시간이 없으면 false 반환") {
|
test("동일한 시간이 없으면 false 반환") {
|
||||||
reservationTimeRepository.existsByStartAt(startAt.plusSeconds(1)) shouldBe false
|
timeRepository.existsByStartAt(startAt.plusSeconds(1)) shouldBe false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -28,7 +28,7 @@ import roomescape.payment.infrastructure.client.TossPaymentClient
|
|||||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||||
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.TimeEntity
|
||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import roomescape.util.*
|
import roomescape.util.*
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
@ -70,7 +70,7 @@ class ReservationControllerTest(
|
|||||||
)
|
)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
paymentClient.confirmPayment(any())
|
paymentClient.confirm(any())
|
||||||
} returns paymentApproveResponse
|
} returns paymentApproveResponse
|
||||||
|
|
||||||
Given {
|
Given {
|
||||||
@ -80,7 +80,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
post("/reservations")
|
post("/reservations")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(201)
|
statusCode(201)
|
||||||
body("data.date", equalTo(reservationRequest.date.toString()))
|
body("data.date", equalTo(reservationRequest.date.toString()))
|
||||||
body("data.status", equalTo(ReservationStatus.CONFIRMED.name))
|
body("data.status", equalTo(ReservationStatus.CONFIRMED.name))
|
||||||
@ -95,7 +94,7 @@ class ReservationControllerTest(
|
|||||||
)
|
)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
paymentClient.confirmPayment(any())
|
paymentClient.confirm(any())
|
||||||
} throws paymentException
|
} throws paymentException
|
||||||
|
|
||||||
Given {
|
Given {
|
||||||
@ -105,7 +104,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
post("/reservations")
|
post("/reservations")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(paymentException.httpStatus.value())
|
statusCode(paymentException.httpStatus.value())
|
||||||
body("errorType", equalTo(paymentException.errorType.name))
|
body("errorType", equalTo(paymentException.errorType.name))
|
||||||
}
|
}
|
||||||
@ -120,7 +118,7 @@ class ReservationControllerTest(
|
|||||||
)
|
)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
paymentClient.confirmPayment(any())
|
paymentClient.confirm(any())
|
||||||
} returns paymentApproveResponse
|
} returns paymentApproveResponse
|
||||||
|
|
||||||
// 예약 저장 과정에서 테마가 없는 예외
|
// 예약 저장 과정에서 테마가 없는 예외
|
||||||
@ -128,7 +126,7 @@ class ReservationControllerTest(
|
|||||||
val expectedException = RoomescapeException(ErrorType.THEME_NOT_FOUND, HttpStatus.BAD_REQUEST)
|
val expectedException = RoomescapeException(ErrorType.THEME_NOT_FOUND, HttpStatus.BAD_REQUEST)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
paymentClient.cancelPayment(any())
|
paymentClient.cancel(any())
|
||||||
} returns PaymentFixture.createCancelResponse()
|
} returns PaymentFixture.createCancelResponse()
|
||||||
|
|
||||||
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
|
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
|
||||||
@ -143,7 +141,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
post("/reservations")
|
post("/reservations")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(expectedException.httpStatus.value())
|
statusCode(expectedException.httpStatus.value())
|
||||||
body("errorType", equalTo(expectedException.errorType.name))
|
body("errorType", equalTo(expectedException.errorType.name))
|
||||||
}
|
}
|
||||||
@ -171,7 +168,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations")
|
get("/reservations")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
||||||
}
|
}
|
||||||
@ -194,7 +190,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations-mine")
|
get("/reservations-mine")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.reservations.size()", equalTo(expectedReservations))
|
body("data.reservations.size()", equalTo(expectedReservations))
|
||||||
}
|
}
|
||||||
@ -216,7 +211,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations/search")
|
get("/reservations/search")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE))
|
header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,7 +224,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations/search")
|
get("/reservations/search")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
||||||
}
|
}
|
||||||
@ -250,7 +243,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations/search")
|
get("/reservations/search")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||||
body("errorType", equalTo(ErrorType.INVALID_DATE_RANGE.name))
|
body("errorType", equalTo(ErrorType.INVALID_DATE_RANGE.name))
|
||||||
}
|
}
|
||||||
@ -267,7 +259,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations/search")
|
get("/reservations/search")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.reservations.size()", equalTo(reservations[member]?.size ?: 0))
|
body("data.reservations.size()", equalTo(reservations[member]?.size ?: 0))
|
||||||
}
|
}
|
||||||
@ -285,7 +276,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations/search")
|
get("/reservations/search")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.reservations.size()", equalTo(themes.filter { it.id == requestThemeId }.size))
|
body("data.reservations.size()", equalTo(themes.filter { it.id == requestThemeId }.size))
|
||||||
}
|
}
|
||||||
@ -304,7 +294,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations/search")
|
get("/reservations/search")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
body("data.reservations.size()", equalTo(reservations.values.sumOf { it.size }))
|
||||||
}
|
}
|
||||||
@ -326,7 +315,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
delete("/reservations/${reservation.id}")
|
delete("/reservations/${reservation.id}")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(302)
|
statusCode(302)
|
||||||
header(HttpHeaders.LOCATION, containsString("/login"))
|
header(HttpHeaders.LOCATION, containsString("/login"))
|
||||||
}
|
}
|
||||||
@ -352,7 +340,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
delete("/reservations/$reservationId")
|
delete("/reservations/$reservationId")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(HttpStatus.NO_CONTENT.value())
|
statusCode(HttpStatus.NO_CONTENT.value())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +367,7 @@ class ReservationControllerTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
every {
|
every {
|
||||||
paymentClient.cancelPayment(any())
|
paymentClient.cancel(any())
|
||||||
} returns PaymentFixture.createCancelResponse()
|
} returns PaymentFixture.createCancelResponse()
|
||||||
|
|
||||||
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
|
val canceledPaymentSizeBeforeApiCall: Long = entityManager.createQuery(
|
||||||
@ -393,7 +380,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
delete("/reservations/${reservation.id}")
|
delete("/reservations/${reservation.id}")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(HttpStatus.NO_CONTENT.value())
|
statusCode(HttpStatus.NO_CONTENT.value())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,8 +395,8 @@ class ReservationControllerTest(
|
|||||||
context("POST /reservations/admin") {
|
context("POST /reservations/admin") {
|
||||||
test("관리자가 예약을 추가하면 결제 대기 상태로 예약 생성") {
|
test("관리자가 예약을 추가하면 결제 대기 상태로 예약 생성") {
|
||||||
val member = login(MemberFixture.create(role = Role.ADMIN))
|
val member = login(MemberFixture.create(role = Role.ADMIN))
|
||||||
val adminRequest: AdminReservationRequest = createRequest().let {
|
val adminRequest: AdminReservationCreateRequest = createRequest().let {
|
||||||
AdminReservationRequest(
|
AdminReservationCreateRequest(
|
||||||
date = it.date,
|
date = it.date,
|
||||||
themeId = it.themeId,
|
themeId = it.themeId,
|
||||||
timeId = it.timeId,
|
timeId = it.timeId,
|
||||||
@ -425,7 +411,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
post("/reservations/admin")
|
post("/reservations/admin")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(201)
|
statusCode(201)
|
||||||
body("data.status", equalTo(ReservationStatus.CONFIRMED_PAYMENT_REQUIRED.name))
|
body("data.status", equalTo(ReservationStatus.CONFIRMED_PAYMENT_REQUIRED.name))
|
||||||
}
|
}
|
||||||
@ -447,7 +432,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations/waiting")
|
get("/reservations/waiting")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE))
|
header(HttpHeaders.CONTENT_TYPE, containsString(MediaType.TEXT_HTML_VALUE))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -463,7 +447,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
get("/reservations/waiting")
|
get("/reservations/waiting")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.reservations.size()", equalTo(expected))
|
body("data.reservations.size()", equalTo(expected))
|
||||||
}
|
}
|
||||||
@ -473,8 +456,8 @@ class ReservationControllerTest(
|
|||||||
context("POST /reservations/waiting") {
|
context("POST /reservations/waiting") {
|
||||||
test("회원이 대기 예약을 추가한다.") {
|
test("회원이 대기 예약을 추가한다.") {
|
||||||
val member = login(MemberFixture.create(role = Role.MEMBER))
|
val member = login(MemberFixture.create(role = Role.MEMBER))
|
||||||
val waitingRequest: WaitingRequest = createRequest().let {
|
val waitingCreateRequest: WaitingCreateRequest = createRequest().let {
|
||||||
WaitingRequest(
|
WaitingCreateRequest(
|
||||||
date = it.date,
|
date = it.date,
|
||||||
themeId = it.themeId,
|
themeId = it.themeId,
|
||||||
timeId = it.timeId
|
timeId = it.timeId
|
||||||
@ -484,11 +467,10 @@ class ReservationControllerTest(
|
|||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||||
body(waitingRequest)
|
body(waitingCreateRequest)
|
||||||
}.When {
|
}.When {
|
||||||
post("/reservations/waiting")
|
post("/reservations/waiting")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(201)
|
statusCode(201)
|
||||||
body("data.member.id", equalTo(member.id!!.toInt()))
|
body("data.member.id", equalTo(member.id!!.toInt()))
|
||||||
body("data.status", equalTo(ReservationStatus.WAITING.name))
|
body("data.status", equalTo(ReservationStatus.WAITING.name))
|
||||||
@ -503,7 +485,7 @@ class ReservationControllerTest(
|
|||||||
val reservation = ReservationFixture.create(
|
val reservation = ReservationFixture.create(
|
||||||
date = reservationRequest.date,
|
date = reservationRequest.date,
|
||||||
theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId),
|
theme = entityManager.find(ThemeEntity::class.java, reservationRequest.themeId),
|
||||||
reservationTime = entityManager.find(ReservationTimeEntity::class.java, reservationRequest.timeId),
|
time = entityManager.find(TimeEntity::class.java, reservationRequest.timeId),
|
||||||
member = member,
|
member = member,
|
||||||
status = ReservationStatus.WAITING
|
status = ReservationStatus.WAITING
|
||||||
)
|
)
|
||||||
@ -513,7 +495,7 @@ class ReservationControllerTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 이미 예약된 시간, 테마로 대기 예약 요청
|
// 이미 예약된 시간, 테마로 대기 예약 요청
|
||||||
val waitingRequest = WaitingRequest(
|
val waitingCreateRequest = WaitingCreateRequest(
|
||||||
date = reservationRequest.date,
|
date = reservationRequest.date,
|
||||||
themeId = reservationRequest.themeId,
|
themeId = reservationRequest.themeId,
|
||||||
timeId = reservationRequest.timeId
|
timeId = reservationRequest.timeId
|
||||||
@ -522,11 +504,10 @@ class ReservationControllerTest(
|
|||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||||
body(waitingRequest)
|
body(waitingCreateRequest)
|
||||||
}.When {
|
}.When {
|
||||||
post("/reservations/waiting")
|
post("/reservations/waiting")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||||
body("errorType", equalTo(ErrorType.HAS_RESERVATION_OR_WAITING.name))
|
body("errorType", equalTo(ErrorType.HAS_RESERVATION_OR_WAITING.name))
|
||||||
}
|
}
|
||||||
@ -551,7 +532,6 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
delete("/reservations/waiting/${waiting.id}")
|
delete("/reservations/waiting/${waiting.id}")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(HttpStatus.NO_CONTENT.value())
|
statusCode(HttpStatus.NO_CONTENT.value())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -575,23 +555,21 @@ class ReservationControllerTest(
|
|||||||
}.When {
|
}.When {
|
||||||
delete("/reservations/waiting/{id}", reservation.id)
|
delete("/reservations/waiting/{id}", reservation.id)
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
body("errorType", equalTo(ErrorType.RESERVATION_NOT_FOUND.name))
|
body("errorType", equalTo(ErrorType.RESERVATION_NOT_FOUND.name))
|
||||||
statusCode(HttpStatus.NOT_FOUND.value())
|
statusCode(HttpStatus.NOT_FOUND.value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context("POST /reservations/waiting/{id}/approve") {
|
context("POST /reservations/waiting/{id}/confirm") {
|
||||||
test("관리자만 승인할 수 있다.") {
|
test("관리자만 승인할 수 있다.") {
|
||||||
login(MemberFixture.create(role = Role.MEMBER))
|
login(MemberFixture.create(role = Role.MEMBER))
|
||||||
|
|
||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
}.When {
|
}.When {
|
||||||
post("/reservations/waiting/1/approve")
|
post("/reservations/waiting/1/confirm")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(302)
|
statusCode(302)
|
||||||
header(HttpHeaders.LOCATION, containsString("/login"))
|
header(HttpHeaders.LOCATION, containsString("/login"))
|
||||||
}
|
}
|
||||||
@ -607,9 +585,8 @@ class ReservationControllerTest(
|
|||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
}.When {
|
}.When {
|
||||||
post("/reservations/waiting/${reservation.id!!}/approve")
|
post("/reservations/waiting/${reservation.id!!}/confirm")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,16 +601,15 @@ class ReservationControllerTest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context("POST /reservations/waiting/{id}/deny") {
|
context("POST /reservations/waiting/{id}/reject") {
|
||||||
test("관리자만 거절할 수 있다.") {
|
test("관리자만 거절할 수 있다.") {
|
||||||
login(MemberFixture.create(role = Role.MEMBER))
|
login(MemberFixture.create(role = Role.MEMBER))
|
||||||
|
|
||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
}.When {
|
}.When {
|
||||||
post("/reservations/waiting/1/deny")
|
post("/reservations/waiting/1/reject")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(302)
|
statusCode(302)
|
||||||
header(HttpHeaders.LOCATION, containsString("/login"))
|
header(HttpHeaders.LOCATION, containsString("/login"))
|
||||||
}
|
}
|
||||||
@ -649,9 +625,8 @@ class ReservationControllerTest(
|
|||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
}.When {
|
}.When {
|
||||||
post("/reservations/waiting/${reservation.id!!}/deny")
|
post("/reservations/waiting/${reservation.id!!}/reject")
|
||||||
}.Then {
|
}.Then {
|
||||||
log().all()
|
|
||||||
statusCode(204)
|
statusCode(204)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -675,7 +650,7 @@ class ReservationControllerTest(
|
|||||||
return ReservationFixture.create(
|
return ReservationFixture.create(
|
||||||
date = date,
|
date = date,
|
||||||
theme = ThemeFixture.create(name = themeName),
|
theme = ThemeFixture.create(name = themeName),
|
||||||
reservationTime = ReservationTimeFixture.create(startAt = time),
|
time = TimeFixture.create(startAt = time),
|
||||||
member = member,
|
member = member,
|
||||||
status = status
|
status = status
|
||||||
).also { it ->
|
).also { it ->
|
||||||
@ -683,7 +658,7 @@ class ReservationControllerTest(
|
|||||||
if (member.id == null) {
|
if (member.id == null) {
|
||||||
entityManager.persist(member)
|
entityManager.persist(member)
|
||||||
}
|
}
|
||||||
entityManager.persist(it.reservationTime)
|
entityManager.persist(it.time)
|
||||||
entityManager.persist(it.theme)
|
entityManager.persist(it.theme)
|
||||||
entityManager.persist(it)
|
entityManager.persist(it)
|
||||||
entityManager.flush()
|
entityManager.flush()
|
||||||
@ -710,14 +685,14 @@ class ReservationControllerTest(
|
|||||||
transactionTemplate.executeWithoutResult {
|
transactionTemplate.executeWithoutResult {
|
||||||
repeat(10) { index ->
|
repeat(10) { index ->
|
||||||
val theme = ThemeFixture.create(name = "theme$index")
|
val theme = ThemeFixture.create(name = "theme$index")
|
||||||
val time = ReservationTimeFixture.create(startAt = LocalTime.now().plusMinutes(index.toLong()))
|
val time = TimeFixture.create(startAt = LocalTime.now().plusMinutes(index.toLong()))
|
||||||
entityManager.persist(theme)
|
entityManager.persist(theme)
|
||||||
entityManager.persist(time)
|
entityManager.persist(time)
|
||||||
|
|
||||||
val reservation = ReservationFixture.create(
|
val reservation = ReservationFixture.create(
|
||||||
date = LocalDate.now().plusDays(index.toLong()),
|
date = LocalDate.now().plusDays(index.toLong()),
|
||||||
theme = theme,
|
theme = theme,
|
||||||
reservationTime = time,
|
time = time,
|
||||||
member = members[index % members.size],
|
member = members[index % members.size],
|
||||||
status = ReservationStatus.CONFIRMED
|
status = ReservationStatus.CONFIRMED
|
||||||
)
|
)
|
||||||
@ -733,15 +708,15 @@ class ReservationControllerTest(
|
|||||||
|
|
||||||
fun createRequest(
|
fun createRequest(
|
||||||
theme: ThemeEntity = ThemeFixture.create(),
|
theme: ThemeEntity = ThemeFixture.create(),
|
||||||
time: ReservationTimeEntity = ReservationTimeFixture.create(),
|
time: TimeEntity = TimeFixture.create(),
|
||||||
): ReservationRequest {
|
): ReservationCreateWithPaymentRequest {
|
||||||
lateinit var reservationRequest: ReservationRequest
|
lateinit var reservationCreateWithPaymentRequest: ReservationCreateWithPaymentRequest
|
||||||
|
|
||||||
transactionTemplate.executeWithoutResult {
|
transactionTemplate.executeWithoutResult {
|
||||||
entityManager.persist(theme)
|
entityManager.persist(theme)
|
||||||
entityManager.persist(time)
|
entityManager.persist(time)
|
||||||
|
|
||||||
reservationRequest = ReservationFixture.createRequest(
|
reservationCreateWithPaymentRequest = ReservationFixture.createRequest(
|
||||||
themeId = theme.id!!,
|
themeId = theme.id!!,
|
||||||
timeId = time.id!!,
|
timeId = time.id!!,
|
||||||
)
|
)
|
||||||
@ -750,7 +725,7 @@ class ReservationControllerTest(
|
|||||||
entityManager.clear()
|
entityManager.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
return reservationRequest
|
return reservationCreateWithPaymentRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
fun login(member: MemberEntity): MemberEntity {
|
fun login(member: MemberEntity): MemberEntity {
|
||||||
@ -13,28 +13,28 @@ import org.springframework.http.MediaType
|
|||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
import roomescape.common.config.JacksonConfig
|
import roomescape.common.config.JacksonConfig
|
||||||
import roomescape.common.exception.ErrorType
|
import roomescape.common.exception.ErrorType
|
||||||
import roomescape.reservation.business.ReservationTimeService
|
import roomescape.reservation.business.TimeService
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository
|
import roomescape.reservation.infrastructure.persistence.TimeRepository
|
||||||
import roomescape.util.ReservationFixture
|
import roomescape.util.ReservationFixture
|
||||||
import roomescape.util.ReservationTimeFixture
|
|
||||||
import roomescape.util.RoomescapeApiTest
|
import roomescape.util.RoomescapeApiTest
|
||||||
import roomescape.util.ThemeFixture
|
import roomescape.util.ThemeFixture
|
||||||
|
import roomescape.util.TimeFixture
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
@WebMvcTest(ReservationTimeController::class)
|
@WebMvcTest(TimeController::class)
|
||||||
@Import(JacksonConfig::class)
|
@Import(JacksonConfig::class)
|
||||||
class ReservationTimeControllerTest(
|
class TimeControllerTest(
|
||||||
val mockMvc: MockMvc,
|
val mockMvc: MockMvc,
|
||||||
) : RoomescapeApiTest() {
|
) : RoomescapeApiTest() {
|
||||||
|
|
||||||
@SpykBean
|
@SpykBean
|
||||||
private lateinit var reservationTimeService: ReservationTimeService
|
private lateinit var timeService: TimeService
|
||||||
|
|
||||||
@MockkBean
|
@MockkBean
|
||||||
private lateinit var reservationTimeRepository: ReservationTimeRepository
|
private lateinit var timeRepository: TimeRepository
|
||||||
|
|
||||||
@MockkBean
|
@MockkBean
|
||||||
private lateinit var reservationRepository: ReservationRepository
|
private lateinit var reservationRepository: ReservationRepository
|
||||||
@ -50,16 +50,15 @@ class ReservationTimeControllerTest(
|
|||||||
|
|
||||||
Then("정상 응답") {
|
Then("정상 응답") {
|
||||||
every {
|
every {
|
||||||
reservationTimeRepository.findAll()
|
timeRepository.findAll()
|
||||||
} returns listOf(
|
} returns listOf(
|
||||||
ReservationTimeFixture.create(id = 1L),
|
TimeFixture.create(id = 1L),
|
||||||
ReservationTimeFixture.create(id = 2L)
|
TimeFixture.create(id = 2L)
|
||||||
)
|
)
|
||||||
|
|
||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
content {
|
content {
|
||||||
@ -78,7 +77,6 @@ class ReservationTimeControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header { string("Location", "/login") }
|
header { string("Location", "/login") }
|
||||||
@ -95,7 +93,7 @@ class ReservationTimeControllerTest(
|
|||||||
loginAsAdmin()
|
loginAsAdmin()
|
||||||
}
|
}
|
||||||
val time = LocalTime.of(10, 0)
|
val time = LocalTime.of(10, 0)
|
||||||
val request = ReservationTimeRequest(startAt = time)
|
val request = TimeCreateRequest(startAt = time)
|
||||||
|
|
||||||
Then("시간 형식이 HH:mm이 아니거나, 범위를 벗어나면 400 응답") {
|
Then("시간 형식이 HH:mm이 아니거나, 범위를 벗어나면 400 응답") {
|
||||||
listOf(
|
listOf(
|
||||||
@ -106,7 +104,6 @@ class ReservationTimeControllerTest(
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = it,
|
body = it,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isBadRequest() }
|
status { isBadRequest() }
|
||||||
}
|
}
|
||||||
@ -115,14 +112,13 @@ class ReservationTimeControllerTest(
|
|||||||
|
|
||||||
Then("정상 응답") {
|
Then("정상 응답") {
|
||||||
every {
|
every {
|
||||||
reservationTimeService.addTime(request)
|
timeService.createTime(request)
|
||||||
} returns ReservationTimeResponse(id = 1, startAt = time)
|
} returns TimeCreateResponse(id = 1, startAt = time)
|
||||||
|
|
||||||
runPostTest(
|
runPostTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = request,
|
body = request,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isCreated() }
|
status { isCreated() }
|
||||||
content {
|
content {
|
||||||
@ -135,14 +131,13 @@ class ReservationTimeControllerTest(
|
|||||||
|
|
||||||
Then("동일한 시간이 존재하면 409 응답") {
|
Then("동일한 시간이 존재하면 409 응답") {
|
||||||
every {
|
every {
|
||||||
reservationTimeRepository.existsByStartAt(time)
|
timeRepository.existsByStartAt(time)
|
||||||
} returns true
|
} returns true
|
||||||
|
|
||||||
runPostTest(
|
runPostTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = request,
|
body = request,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isConflict() }
|
status { isConflict() }
|
||||||
content {
|
content {
|
||||||
@ -160,8 +155,7 @@ class ReservationTimeControllerTest(
|
|||||||
runPostTest(
|
runPostTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = ReservationTimeFixture.create(),
|
body = TimeFixture.create(),
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header { string("Location", "/login") }
|
header { string("Location", "/login") }
|
||||||
@ -180,13 +174,12 @@ class ReservationTimeControllerTest(
|
|||||||
|
|
||||||
Then("정상 응답") {
|
Then("정상 응답") {
|
||||||
every {
|
every {
|
||||||
reservationTimeService.removeTimeById(1L)
|
timeService.deleteTime(1L)
|
||||||
} returns Unit
|
} returns Unit
|
||||||
|
|
||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isNoContent() }
|
status { isNoContent() }
|
||||||
}
|
}
|
||||||
@ -195,18 +188,17 @@ class ReservationTimeControllerTest(
|
|||||||
Then("없는 시간을 조회하면 400 응답") {
|
Then("없는 시간을 조회하면 400 응답") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
every {
|
every {
|
||||||
reservationTimeRepository.findByIdOrNull(id)
|
timeRepository.findByIdOrNull(id)
|
||||||
} returns null
|
} returns null
|
||||||
|
|
||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = "/times/$id",
|
endpoint = "/times/$id",
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isBadRequest() }
|
status { isBadRequest() }
|
||||||
content {
|
content {
|
||||||
contentType(MediaType.APPLICATION_JSON)
|
contentType(MediaType.APPLICATION_JSON)
|
||||||
jsonPath("$.errorType") { value(ErrorType.RESERVATION_TIME_NOT_FOUND.name) }
|
jsonPath("$.errorType") { value(ErrorType.TIME_NOT_FOUND.name) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,17 +206,16 @@ class ReservationTimeControllerTest(
|
|||||||
Then("예약이 있는 시간을 삭제하면 409 응답") {
|
Then("예약이 있는 시간을 삭제하면 409 응답") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
every {
|
every {
|
||||||
reservationTimeRepository.findByIdOrNull(id)
|
timeRepository.findByIdOrNull(id)
|
||||||
} returns ReservationTimeFixture.create(id = id)
|
} returns TimeFixture.create(id = id)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
reservationRepository.findByReservationTime(any())
|
reservationRepository.findByTime(any())
|
||||||
} returns listOf(ReservationFixture.create())
|
} returns listOf(ReservationFixture.create())
|
||||||
|
|
||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = "/times/$id",
|
endpoint = "/times/$id",
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isConflict() }
|
status { isConflict() }
|
||||||
content {
|
content {
|
||||||
@ -242,7 +233,6 @@ class ReservationTimeControllerTest(
|
|||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header { string("Location", "/login") }
|
header { string("Location", "/login") }
|
||||||
@ -258,13 +248,13 @@ class ReservationTimeControllerTest(
|
|||||||
val themeId = 1L
|
val themeId = 1L
|
||||||
|
|
||||||
When("저장된 예약 시간이 있으면") {
|
When("저장된 예약 시간이 있으면") {
|
||||||
val times: List<ReservationTimeEntity> = listOf(
|
val times: List<TimeEntity> = listOf(
|
||||||
ReservationTimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)),
|
TimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)),
|
||||||
ReservationTimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0))
|
TimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0))
|
||||||
)
|
)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
reservationTimeRepository.findAll()
|
timeRepository.findAll()
|
||||||
} returns times
|
} returns times
|
||||||
|
|
||||||
Then("그 시간과, 해당 날짜와 테마에 대한 예약 여부가 담긴 목록을 응답") {
|
Then("그 시간과, 해당 날짜와 테마에 대한 예약 여부가 담긴 목록을 응답") {
|
||||||
@ -276,28 +266,27 @@ class ReservationTimeControllerTest(
|
|||||||
id = 1L,
|
id = 1L,
|
||||||
date = date,
|
date = date,
|
||||||
theme = ThemeFixture.create(id = themeId),
|
theme = ThemeFixture.create(id = themeId),
|
||||||
reservationTime = times[0]
|
time = times[0]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = runGetTest(
|
val response = runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = "/times/filter?date=$date&themeId=$themeId",
|
endpoint = "/times/search?date=$date&themeId=$themeId",
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
content {
|
content {
|
||||||
contentType(MediaType.APPLICATION_JSON)
|
contentType(MediaType.APPLICATION_JSON)
|
||||||
}
|
}
|
||||||
}.andReturn().readValue(ReservationTimeInfosResponse::class.java)
|
}.andReturn().readValue(TimeWithAvailabilityListResponse::class.java)
|
||||||
|
|
||||||
assertSoftly(response.times) {
|
assertSoftly(response.times) {
|
||||||
this shouldHaveSize times.size
|
this shouldHaveSize times.size
|
||||||
this[0].id shouldBe times[0].id
|
this[0].id shouldBe times[0].id
|
||||||
this[0].alreadyBooked shouldBe true
|
this[0].isAvailable shouldBe false
|
||||||
|
|
||||||
this[1].id shouldBe times[1].id
|
this[1].id shouldBe times[1].id
|
||||||
this[1].alreadyBooked shouldBe false
|
this[1].isAvailable shouldBe true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ class ThemeServiceTest : FunSpec({
|
|||||||
} returns null
|
} returns null
|
||||||
|
|
||||||
val exception = shouldThrow<RoomescapeException> {
|
val exception = shouldThrow<RoomescapeException> {
|
||||||
themeService.findThemeById(themeId)
|
themeService.findById(themeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exception.errorType shouldBe ErrorType.THEME_NOT_FOUND
|
exception.errorType shouldBe ErrorType.THEME_NOT_FOUND
|
||||||
@ -51,7 +51,7 @@ class ThemeServiceTest : FunSpec({
|
|||||||
themeRepository.findAll()
|
themeRepository.findAll()
|
||||||
} returns themes
|
} returns themes
|
||||||
|
|
||||||
assertSoftly(themeService.findAllThemes()) {
|
assertSoftly(themeService.findThemes()) {
|
||||||
this.themes.size shouldBe themes.size
|
this.themes.size shouldBe themes.size
|
||||||
this.themes[0].name shouldBe "t1"
|
this.themes[0].name shouldBe "t1"
|
||||||
this.themes[1].name shouldBe "t2"
|
this.themes[1].name shouldBe "t2"
|
||||||
@ -68,7 +68,7 @@ class ThemeServiceTest : FunSpec({
|
|||||||
} returns true
|
} returns true
|
||||||
|
|
||||||
val exception = shouldThrow<RoomescapeException> {
|
val exception = shouldThrow<RoomescapeException> {
|
||||||
themeService.save(ThemeRequest(
|
themeService.createTheme(ThemeRequest(
|
||||||
name = name,
|
name = name,
|
||||||
description = "Description",
|
description = "Description",
|
||||||
thumbnail = "http://example.com/thumbnail.jpg"
|
thumbnail = "http://example.com/thumbnail.jpg"
|
||||||
@ -91,7 +91,7 @@ class ThemeServiceTest : FunSpec({
|
|||||||
} returns true
|
} returns true
|
||||||
|
|
||||||
val exception = shouldThrow<RoomescapeException> {
|
val exception = shouldThrow<RoomescapeException> {
|
||||||
themeService.deleteById(themeId)
|
themeService.deleteTheme(themeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertSoftly(exception) {
|
assertSoftly(exception) {
|
||||||
@ -28,7 +28,7 @@ class ThemeRepositoryTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("지난 10일간 예약 수가 가장 많은 테마 5개를 조회한다.") {
|
test("지난 10일간 예약 수가 가장 많은 테마 5개를 조회한다.") {
|
||||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
themeRepository.findPopularThemes(
|
||||||
LocalDate.now().minusDays(10),
|
LocalDate.now().minusDays(10),
|
||||||
LocalDate.now().minusDays(1),
|
LocalDate.now().minusDays(1),
|
||||||
5
|
5
|
||||||
@ -41,7 +41,7 @@ class ThemeRepositoryTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("8일 전부터 5일 전까지 예약 수가 가장 많은 테마 3개를 조회한다.") {
|
test("8일 전부터 5일 전까지 예약 수가 가장 많은 테마 3개를 조회한다.") {
|
||||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
themeRepository.findPopularThemes(
|
||||||
LocalDate.now().minusDays(8),
|
LocalDate.now().minusDays(8),
|
||||||
LocalDate.now().minusDays(5),
|
LocalDate.now().minusDays(5),
|
||||||
3
|
3
|
||||||
@ -61,7 +61,7 @@ class ThemeRepositoryTest(
|
|||||||
date = LocalDate.now().minusDays(5),
|
date = LocalDate.now().minusDays(5),
|
||||||
)
|
)
|
||||||
|
|
||||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
themeRepository.findPopularThemes(
|
||||||
LocalDate.now().minusDays(6),
|
LocalDate.now().minusDays(6),
|
||||||
LocalDate.now().minusDays(4),
|
LocalDate.now().minusDays(4),
|
||||||
5
|
5
|
||||||
@ -74,7 +74,7 @@ class ThemeRepositoryTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("입력된 갯수보다 조회된 갯수가 작으면, 조회된 갯수만큼 반환한다.") {
|
test("입력된 갯수보다 조회된 갯수가 작으면, 조회된 갯수만큼 반환한다.") {
|
||||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
themeRepository.findPopularThemes(
|
||||||
LocalDate.now().minusDays(10),
|
LocalDate.now().minusDays(10),
|
||||||
LocalDate.now().minusDays(6),
|
LocalDate.now().minusDays(6),
|
||||||
10
|
10
|
||||||
@ -84,7 +84,7 @@ class ThemeRepositoryTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("입력된 갯수보다 조회된 갯수가 많으면, 입력된 갯수만큼 반환한다.") {
|
test("입력된 갯수보다 조회된 갯수가 많으면, 입력된 갯수만큼 반환한다.") {
|
||||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
themeRepository.findPopularThemes(
|
||||||
LocalDate.now().minusDays(10),
|
LocalDate.now().minusDays(10),
|
||||||
LocalDate.now().minusDays(1),
|
LocalDate.now().minusDays(1),
|
||||||
15
|
15
|
||||||
@ -94,7 +94,7 @@ class ThemeRepositoryTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("입력된 날짜 범위에 예약된 테마가 없을 경우 빈 리스트를 반환한다.") {
|
test("입력된 날짜 범위에 예약된 테마가 없을 경우 빈 리스트를 반환한다.") {
|
||||||
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
|
themeRepository.findPopularThemes(
|
||||||
LocalDate.now().plusDays(1),
|
LocalDate.now().plusDays(1),
|
||||||
LocalDate.now().plusDays(10),
|
LocalDate.now().plusDays(10),
|
||||||
5
|
5
|
||||||
@ -3,12 +3,12 @@ package roomescape.theme.util
|
|||||||
import jakarta.persistence.EntityManager
|
import jakarta.persistence.EntityManager
|
||||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity
|
import roomescape.reservation.infrastructure.persistence.TimeEntity
|
||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import roomescape.util.MemberFixture
|
import roomescape.util.MemberFixture
|
||||||
import roomescape.util.ReservationFixture
|
import roomescape.util.ReservationFixture
|
||||||
import roomescape.util.ReservationTimeFixture
|
|
||||||
import roomescape.util.ThemeFixture
|
import roomescape.util.ThemeFixture
|
||||||
|
import roomescape.util.TimeFixture
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ object TestThemeCreateUtil {
|
|||||||
val member: MemberEntity = MemberFixture.create().also { entityManager.persist(it) }
|
val member: MemberEntity = MemberFixture.create().also { entityManager.persist(it) }
|
||||||
|
|
||||||
for (i in 1..reservedCount) {
|
for (i in 1..reservedCount) {
|
||||||
val time: ReservationTimeEntity = ReservationTimeFixture.create(
|
val time: TimeEntity = TimeFixture.create(
|
||||||
startAt = LocalTime.now().plusMinutes(i.toLong())
|
startAt = LocalTime.now().plusMinutes(i.toLong())
|
||||||
).also { entityManager.persist(it) }
|
).also { entityManager.persist(it) }
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ object TestThemeCreateUtil {
|
|||||||
date = date,
|
date = date,
|
||||||
theme = themeEntity,
|
theme = themeEntity,
|
||||||
member = member,
|
member = member,
|
||||||
reservationTime = time,
|
time = time,
|
||||||
status = ReservationStatus.CONFIRMED
|
status = ReservationStatus.CONFIRMED
|
||||||
).also { entityManager.persist(it) }
|
).also { entityManager.persist(it) }
|
||||||
}
|
}
|
||||||
@ -9,22 +9,15 @@ import org.hamcrest.Matchers.equalTo
|
|||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
import org.springframework.boot.test.web.server.LocalServerPort
|
import org.springframework.boot.test.web.server.LocalServerPort
|
||||||
import org.springframework.transaction.support.TransactionTemplate
|
import org.springframework.transaction.support.TransactionTemplate
|
||||||
import roomescape.theme.business.ThemeService
|
|
||||||
import roomescape.theme.util.TestThemeCreateUtil
|
import roomescape.theme.util.TestThemeCreateUtil
|
||||||
import roomescape.util.CleanerMode
|
import roomescape.util.CleanerMode
|
||||||
import roomescape.util.DatabaseCleanerExtension
|
import roomescape.util.DatabaseCleanerExtension
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /themes/most-reserved-last-week API 테스트
|
|
||||||
* 상세 테스트는 Repository 테스트에서 진행
|
|
||||||
* 날짜 범위, 예약 수만 검증
|
|
||||||
*/
|
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
class MostReservedThemeAPITest(
|
class MostReservedThemeApiTest(
|
||||||
@LocalServerPort val port: Int,
|
@LocalServerPort val port: Int,
|
||||||
val themeService: ThemeService,
|
|
||||||
val transactionTemplate: TransactionTemplate,
|
val transactionTemplate: TransactionTemplate,
|
||||||
val entityManager: EntityManager,
|
val entityManager: EntityManager,
|
||||||
) : FunSpec({
|
) : FunSpec({
|
||||||
@ -53,59 +46,55 @@ class MostReservedThemeAPITest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context("가장 많이 예약된 테마를 조회할 때, ") {
|
context("지난 주 가장 많이 예약된 테마 API") {
|
||||||
val endpoint = "/themes/most-reserved-last-week"
|
val endpoint = "/themes/most-reserved-last-week"
|
||||||
test("갯수를 입력하지 않으면 10개를 반환한다.") {
|
|
||||||
|
test("count 파라미터가 없으면 10개를 반환한다") {
|
||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
} When {
|
} When {
|
||||||
get(endpoint)
|
get(endpoint)
|
||||||
} Then {
|
} Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.themes.size()", equalTo(10))
|
body("data.themes.size()", equalTo(10))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("입력된 갯수가 조회된 갯수보다 크면 조회된 갯수만큼 반환한다.") {
|
test("조회된 테마가 count보다 적으면 조회된 만큼 반환한다") {
|
||||||
val count = 15
|
val count = 15
|
||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
} When {
|
|
||||||
param("count", count)
|
param("count", count)
|
||||||
get("/themes/most-reserved-last-week")
|
} When {
|
||||||
|
get(endpoint)
|
||||||
} Then {
|
} Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.themes.size()", equalTo(10))
|
body("data.themes.size()", equalTo(10))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("입력된 갯수가 조회된 갯수보다 작으면 입력된 갯수만큼 반환한다.") {
|
test("조회된 테마가 count보다 많으면 count만큼 반환한다") {
|
||||||
val count = 5
|
val count = 5
|
||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
} When {
|
|
||||||
param("count", count)
|
param("count", count)
|
||||||
get("/themes/most-reserved-last-week")
|
} When {
|
||||||
|
get(endpoint)
|
||||||
} Then {
|
} Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.themes.size()", equalTo(count))
|
body("data.themes.size()", equalTo(count))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("7일 전 부터 1일 전 까지 예약된 테마를 대상으로 한다.") {
|
test("지난 7일 동안의 예약만 집계한다") {
|
||||||
// 현재 저장된 데이터는 지난 7일간 예약된 테마 10개와 8일 전 예약된 테마 1개
|
// 8일 전에 예약된 테마는 집계에서 제외되어야 한다.
|
||||||
// 8일 전 예약된 테마는 제외되어야 하므로, 10개가 조회되어야 한다.
|
|
||||||
val count = 11
|
val count = 11
|
||||||
Given {
|
Given {
|
||||||
port(port)
|
port(port)
|
||||||
} When {
|
|
||||||
param("count", count)
|
param("count", count)
|
||||||
get("/themes/most-reserved-last-week")
|
} When {
|
||||||
|
get(endpoint)
|
||||||
} Then {
|
} Then {
|
||||||
log().all()
|
|
||||||
statusCode(200)
|
statusCode(200)
|
||||||
body("data.themes.size()", equalTo(10))
|
body("data.themes.size()", equalTo(10))
|
||||||
}
|
}
|
||||||
@ -36,7 +36,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header {
|
header {
|
||||||
@ -61,7 +60,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
val response: ThemesResponse = runGetTest(
|
val response: ThemesResponse = runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
content {
|
content {
|
||||||
@ -92,7 +90,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = request,
|
body = request,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header {
|
header {
|
||||||
@ -109,7 +106,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = request,
|
body = request,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
|
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
|
||||||
@ -129,7 +125,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = request,
|
body = request,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isConflict() }
|
status { isConflict() }
|
||||||
jsonPath("$.errorType") { value("THEME_DUPLICATED") }
|
jsonPath("$.errorType") { value("THEME_DUPLICATED") }
|
||||||
@ -153,7 +148,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = request,
|
body = request,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isBadRequest() }
|
status { isBadRequest() }
|
||||||
}
|
}
|
||||||
@ -201,7 +195,7 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
every {
|
every {
|
||||||
themeService.save(request)
|
themeService.createTheme(request)
|
||||||
} returns ThemeResponse(
|
} returns ThemeResponse(
|
||||||
id = theme.id!!,
|
id = theme.id!!,
|
||||||
name = theme.name,
|
name = theme.name,
|
||||||
@ -214,7 +208,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
body = request,
|
body = request,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isCreated() }
|
status { isCreated() }
|
||||||
header {
|
header {
|
||||||
@ -239,7 +232,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header {
|
header {
|
||||||
@ -255,7 +247,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
|
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
|
||||||
@ -274,7 +265,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isConflict() }
|
status { isConflict() }
|
||||||
jsonPath("$.errorType") { value("THEME_IS_USED_CONFLICT") }
|
jsonPath("$.errorType") { value("THEME_IS_USED_CONFLICT") }
|
||||||
@ -297,7 +287,6 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
|||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isNoContent() }
|
status { isNoContent() }
|
||||||
}
|
}
|
||||||
@ -4,20 +4,21 @@ import roomescape.auth.infrastructure.jwt.JwtHandler
|
|||||||
import roomescape.auth.web.LoginRequest
|
import roomescape.auth.web.LoginRequest
|
||||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||||
import roomescape.member.infrastructure.persistence.Role
|
import roomescape.member.infrastructure.persistence.Role
|
||||||
|
import roomescape.payment.infrastructure.client.PaymentApproveRequest
|
||||||
|
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
||||||
import roomescape.payment.web.PaymentApprove
|
import roomescape.payment.web.PaymentCancelRequest
|
||||||
import roomescape.payment.web.PaymentCancel
|
import roomescape.payment.web.PaymentCancelResponse
|
||||||
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.TimeEntity
|
||||||
import roomescape.reservation.web.ReservationRequest
|
import roomescape.reservation.web.ReservationCreateWithPaymentRequest
|
||||||
import roomescape.reservation.web.WaitingRequest
|
import roomescape.reservation.web.WaitingCreateRequest
|
||||||
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
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
object MemberFixture {
|
object MemberFixture {
|
||||||
const val NOT_LOGGED_IN_USERID: Long = 0
|
const val NOT_LOGGED_IN_USERID: Long = 0
|
||||||
@ -53,11 +54,11 @@ object MemberFixture {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
object ReservationTimeFixture {
|
object TimeFixture {
|
||||||
fun create(
|
fun create(
|
||||||
id: Long? = null,
|
id: Long? = null,
|
||||||
startAt: LocalTime = LocalTime.now().plusHours(1),
|
startAt: LocalTime = LocalTime.now().plusHours(1),
|
||||||
): ReservationTimeEntity = ReservationTimeEntity(id, startAt)
|
): TimeEntity = TimeEntity(id, startAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
object ThemeFixture {
|
object ThemeFixture {
|
||||||
@ -74,10 +75,10 @@ object ReservationFixture {
|
|||||||
id: Long? = null,
|
id: Long? = null,
|
||||||
date: LocalDate = LocalDate.now().plusWeeks(1),
|
date: LocalDate = LocalDate.now().plusWeeks(1),
|
||||||
theme: ThemeEntity = ThemeFixture.create(),
|
theme: ThemeEntity = ThemeFixture.create(),
|
||||||
reservationTime: ReservationTimeEntity = ReservationTimeFixture.create(),
|
time: TimeEntity = TimeFixture.create(),
|
||||||
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, time, theme, member, status)
|
||||||
|
|
||||||
fun createRequest(
|
fun createRequest(
|
||||||
date: LocalDate = LocalDate.now().plusWeeks(1),
|
date: LocalDate = LocalDate.now().plusWeeks(1),
|
||||||
@ -87,7 +88,7 @@ object ReservationFixture {
|
|||||||
orderId: String = "orderId",
|
orderId: String = "orderId",
|
||||||
amount: Long = 10000L,
|
amount: Long = 10000L,
|
||||||
paymentType: String = "NORMAL",
|
paymentType: String = "NORMAL",
|
||||||
): ReservationRequest = ReservationRequest(
|
): ReservationCreateWithPaymentRequest = ReservationCreateWithPaymentRequest(
|
||||||
date = date,
|
date = date,
|
||||||
timeId = timeId,
|
timeId = timeId,
|
||||||
themeId = themeId,
|
themeId = themeId,
|
||||||
@ -101,7 +102,7 @@ object ReservationFixture {
|
|||||||
date: LocalDate = LocalDate.now().plusWeeks(1),
|
date: LocalDate = LocalDate.now().plusWeeks(1),
|
||||||
themeId: Long = 1L,
|
themeId: Long = 1L,
|
||||||
timeId: Long = 1L
|
timeId: Long = 1L
|
||||||
): WaitingRequest = WaitingRequest(
|
): WaitingCreateRequest = WaitingCreateRequest(
|
||||||
date = date,
|
date = date,
|
||||||
timeId = timeId,
|
timeId = timeId,
|
||||||
themeId = themeId
|
themeId = themeId
|
||||||
@ -156,27 +157,27 @@ object PaymentFixture {
|
|||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
fun createApproveRequest(): PaymentApprove.Request = PaymentApprove.Request(
|
fun createApproveRequest(): PaymentApproveRequest = PaymentApproveRequest(
|
||||||
paymentKey = PAYMENT_KEY,
|
paymentKey = PAYMENT_KEY,
|
||||||
orderId = ORDER_ID,
|
orderId = ORDER_ID,
|
||||||
amount = AMOUNT,
|
amount = AMOUNT,
|
||||||
paymentType = "CARD"
|
paymentType = "CARD"
|
||||||
)
|
)
|
||||||
|
|
||||||
fun createApproveResponse(): PaymentApprove.Response = PaymentApprove.Response(
|
fun createApproveResponse(): PaymentApproveResponse = PaymentApproveResponse(
|
||||||
paymentKey = PAYMENT_KEY,
|
paymentKey = PAYMENT_KEY,
|
||||||
orderId = ORDER_ID,
|
orderId = ORDER_ID,
|
||||||
approvedAt = OffsetDateTime.now(),
|
approvedAt = OffsetDateTime.now(),
|
||||||
totalAmount = AMOUNT
|
totalAmount = AMOUNT
|
||||||
)
|
)
|
||||||
|
|
||||||
fun createCancelRequest(): PaymentCancel.Request = PaymentCancel.Request(
|
fun createCancelRequest(): PaymentCancelRequest = PaymentCancelRequest(
|
||||||
paymentKey = PAYMENT_KEY,
|
paymentKey = PAYMENT_KEY,
|
||||||
amount = AMOUNT,
|
amount = AMOUNT,
|
||||||
cancelReason = "Test Cancel"
|
cancelReason = "Test Cancel"
|
||||||
)
|
)
|
||||||
|
|
||||||
fun createCancelResponse(): PaymentCancel.Response = PaymentCancel.Response(
|
fun createCancelResponse(): PaymentCancelResponse = PaymentCancelResponse(
|
||||||
cancelStatus = "SUCCESS",
|
cancelStatus = "SUCCESS",
|
||||||
cancelReason = "Test Cancel",
|
cancelReason = "Test Cancel",
|
||||||
cancelAmount = AMOUNT,
|
cancelAmount = AMOUNT,
|
||||||
@ -24,7 +24,6 @@ class PageControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = it,
|
endpoint = it,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
}
|
}
|
||||||
@ -36,7 +35,6 @@ class PageControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = it,
|
endpoint = it,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
}
|
}
|
||||||
@ -48,7 +46,6 @@ class PageControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = it,
|
endpoint = it,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
}
|
}
|
||||||
@ -66,7 +63,6 @@ class PageControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = it,
|
endpoint = it,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
}
|
}
|
||||||
@ -80,7 +76,6 @@ class PageControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = it,
|
endpoint = it,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header {
|
header {
|
||||||
@ -101,7 +96,6 @@ class PageControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = it,
|
endpoint = it,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
}
|
}
|
||||||
@ -112,7 +106,6 @@ class PageControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = it,
|
endpoint = it,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { isOk() }
|
status { isOk() }
|
||||||
}
|
}
|
||||||
@ -126,7 +119,6 @@ class PageControllerTest(
|
|||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = it,
|
endpoint = it,
|
||||||
log = true
|
|
||||||
) {
|
) {
|
||||||
status { is3xxRedirection() }
|
status { is3xxRedirection() }
|
||||||
header {
|
header {
|
||||||
Loading…
x
Reference in New Issue
Block a user