test: ReservationWithPaymentService 및 테스트 코틀린 전환

This commit is contained in:
이상진 2025-07-21 10:32:00 +09:00
parent 88a1a8e4c6
commit 5ddfd02572
7 changed files with 170 additions and 215 deletions

View File

@ -5,6 +5,7 @@ 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 {
@ -60,6 +61,6 @@ fun PaymentEntity.toReservationPaymentResponse(): ReservationPaymentResponse = R
orderId = this.orderId,
paymentKey = this.paymentKey,
totalAmount = this.totalAmount,
reservation = ReservationResponse.from(this.reservation),
reservation = this.reservation.toResponse(),
approvedAt = this.approvedAt
)

View File

@ -1,56 +1,58 @@
package roomescape.reservation.business;
package roomescape.reservation.business
import java.time.OffsetDateTime;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import roomescape.payment.business.PaymentService;
import roomescape.payment.web.PaymentApprove;
import roomescape.payment.web.PaymentCancel;
import roomescape.payment.web.ReservationPaymentResponse;
import roomescape.reservation.infrastructure.persistence.ReservationEntity;
import roomescape.reservation.web.ReservationRequest;
import roomescape.reservation.web.ReservationResponse;
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import roomescape.payment.business.PaymentService
import roomescape.payment.web.PaymentApprove
import roomescape.payment.web.PaymentCancel
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.web.ReservationRequest
import roomescape.reservation.web.ReservationResponse
import java.time.OffsetDateTime
@Service
@Transactional
public class ReservationWithPaymentService {
class ReservationWithPaymentService(
private val reservationService: ReservationService,
private val paymentService: PaymentService
) {
fun addReservationWithPayment(
request: ReservationRequest,
paymentInfo: PaymentApprove.Response,
memberId: Long
): ReservationResponse {
val reservation: ReservationEntity = reservationService.addReservation(request, memberId)
private final ReservationService reservationService;
private final PaymentService paymentService;
public ReservationWithPaymentService(ReservationService reservationService,
PaymentService paymentService) {
this.reservationService = reservationService;
this.paymentService = paymentService;
return paymentService.savePayment(paymentInfo, reservation)
.reservation
}
public ReservationResponse addReservationWithPayment(ReservationRequest request,
PaymentApprove.Response paymentInfo,
Long memberId) {
ReservationEntity reservation = reservationService.addReservation(request, memberId);
ReservationPaymentResponse reservationPaymentResponse = paymentService.savePayment(paymentInfo, reservation);
return reservationPaymentResponse.reservation();
fun saveCanceledPayment(
cancelInfo: PaymentCancel.Response,
approvedAt: OffsetDateTime,
paymentKey: String
) {
paymentService.saveCanceledPayment(cancelInfo, approvedAt, paymentKey)
}
public void saveCanceledPayment(PaymentCancel.Response cancelInfo, OffsetDateTime approvedAt, String paymentKey) {
paymentService.saveCanceledPayment(cancelInfo, approvedAt, paymentKey);
}
fun removeReservationWithPayment(
reservationId: Long,
memberId: Long
): PaymentCancel.Request {
val paymentCancelRequest = paymentService.cancelPaymentByAdmin(reservationId)
reservationService.removeReservationById(reservationId, memberId)
public PaymentCancel.Request removeReservationWithPayment(Long reservationId, Long memberId) {
PaymentCancel.Request paymentCancelRequest = paymentService.cancelPaymentByAdmin(reservationId);
reservationService.removeReservationById(reservationId, memberId);
return paymentCancelRequest;
return paymentCancelRequest
}
@Transactional(readOnly = true)
public boolean isNotPaidReservation(Long reservationId) {
return !paymentService.isReservationPaid(reservationId);
}
fun isNotPaidReservation(reservationId: Long): Boolean = !paymentService.isReservationPaid(reservationId)
public void updateCanceledTime(String paymentKey, OffsetDateTime canceledAt) {
paymentService.updateCanceledTime(paymentKey, canceledAt);
fun updateCanceledTime(
paymentKey: String,
canceledAt: OffsetDateTime
) {
paymentService.updateCanceledTime(paymentKey, canceledAt)
}
}

View File

@ -3,7 +3,6 @@ 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.MemberResponse.Companion.fromEntity
import roomescape.member.web.toResponse
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationStatus
@ -78,9 +77,9 @@ data class ReservationResponse(
return ReservationResponse(
reservation.id!!,
reservation.date,
fromEntity(reservation.member),
ReservationTimeResponse.Companion.from(reservation.reservationTime),
ThemeResponse.Companion.from(reservation.theme),
reservation.member.toResponse(),
reservation.reservationTime.toResponse(),
reservation.theme.toResponse(),
reservation.reservationStatus
)
}

View File

@ -6,6 +6,7 @@ import io.kotest.matchers.shouldBe
import jakarta.persistence.EntityManager
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.util.PaymentFixture
import roomescape.util.ReservationFixture
@ -15,23 +16,23 @@ class PaymentRepositoryTest(
@Autowired val entityManager: EntityManager
) : FunSpec() {
var reservationId: Long = 0L
lateinit var reservation: ReservationEntity
init {
context("existsByReservationId") {
beforeTest {
reservationId = setupReservation()
PaymentFixture.create(reservationId = reservationId)
reservation = setupReservation()
PaymentFixture.create(reservation = reservation)
.also { paymentRepository.save(it) }
}
test("true") {
paymentRepository.existsByReservationId(reservationId)
paymentRepository.existsByReservationId(reservation.id!!)
.also { it shouldBe true }
}
test("false") {
paymentRepository.existsByReservationId(reservationId + 1)
paymentRepository.existsByReservationId(reservation.id!! + 1L)
.also { it shouldBe false }
}
}
@ -40,20 +41,20 @@ class PaymentRepositoryTest(
lateinit var paymentKey: String
beforeTest {
reservationId = setupReservation()
paymentKey = PaymentFixture.create(reservationId = reservationId)
reservation = setupReservation()
paymentKey = PaymentFixture.create(reservation = reservation)
.also { paymentRepository.save(it) }
.paymentKey
}
test("정상 반환") {
paymentRepository.findPaymentKeyByReservationId(reservationId)
paymentRepository.findPaymentKeyByReservationId(reservation.id!!)
?.let { it shouldBe paymentKey }
?: throw AssertionError("Unexpected null value")
}
test("null 반환") {
paymentRepository.findPaymentKeyByReservationId(reservationId + 1)
paymentRepository.findPaymentKeyByReservationId(reservation.id!! + 1)
.also { it shouldBe null }
}
}
@ -62,8 +63,8 @@ class PaymentRepositoryTest(
lateinit var payment: PaymentEntity
beforeTest {
reservationId = setupReservation()
payment = PaymentFixture.create(reservationId = reservationId)
reservation = setupReservation()
payment = PaymentFixture.create(reservation = reservation)
.also { paymentRepository.save(it) }
}
@ -89,7 +90,7 @@ class PaymentRepositoryTest(
}
}
private fun setupReservation(): Long {
private fun setupReservation(): ReservationEntity {
return ReservationFixture.create().also {
entityManager.persist(it.member)
entityManager.persist(it.theme)
@ -98,6 +99,6 @@ class PaymentRepositoryTest(
entityManager.flush()
entityManager.clear()
}.id!!
}
}
}

View File

@ -1,170 +1,122 @@
package roomescape.reservation.business;
package roomescape.reservation.business
import static org.assertj.core.api.Assertions.*;
import io.kotest.assertions.assertSoftly
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import roomescape.payment.business.PaymentService
import roomescape.payment.infrastructure.persistence.PaymentEntity
import roomescape.payment.web.PaymentCancel
import roomescape.payment.web.toReservationPaymentResponse
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationStatus
import roomescape.reservation.web.ReservationRequest
import roomescape.reservation.web.ReservationResponse
import roomescape.util.*
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
class ReservationWithPaymentServiceTest : FunSpec({
val reservationService: ReservationService = mockk()
val paymentService: PaymentService = mockk()
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
val reservationWithPaymentService = ReservationWithPaymentService(
reservationService = reservationService,
paymentService = paymentService
)
import roomescape.member.infrastructure.persistence.MemberEntity;
import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.infrastructure.persistence.Role;
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository;
import roomescape.payment.infrastructure.persistence.PaymentEntity;
import roomescape.payment.infrastructure.persistence.PaymentRepository;
import roomescape.payment.web.PaymentApprove;
import roomescape.payment.web.PaymentCancel;
import roomescape.reservation.infrastructure.persistence.ReservationEntity;
import roomescape.reservation.infrastructure.persistence.ReservationRepository;
import roomescape.reservation.infrastructure.persistence.ReservationStatus;
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity;
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository;
import roomescape.reservation.web.ReservationRequest;
import roomescape.reservation.web.ReservationResponse;
import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.infrastructure.persistence.ThemeRepository;
val reservationRequest: ReservationRequest = ReservationFixture.createRequest()
val paymentApproveResponse = PaymentFixture.createApproveResponse()
val memberId = 1L
val reservationEntity: ReservationEntity = ReservationFixture.create(
id = 1L,
date = reservationRequest.date,
reservationTime = ReservationTimeFixture.create(id = reservationRequest.timeId),
theme = ThemeFixture.create(id = reservationRequest.themeId),
member = MemberFixture.create(id = memberId),
status = ReservationStatus.CONFIRMED
)
val paymentEntity: PaymentEntity = PaymentFixture.create(
id = 1L,
orderId = reservationRequest.orderId,
paymentKey = reservationRequest.paymentKey,
totalAmount = reservationRequest.amount,
reservation = reservationEntity,
)
@SpringBootTest
@Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
class ReservationWithPaymentServiceTest {
context("addReservationWithPayment") {
test("예약 및 결제 정보를 저장한다.") {
every {
reservationService.addReservation(reservationRequest, memberId)
} returns reservationEntity
@Autowired
private ReservationWithPaymentService reservationWithPaymentService;
@Autowired
private ReservationRepository reservationRepository;
@Autowired
private MemberRepository memberRepository;
@Autowired
private ReservationTimeRepository reservationTimeRepository;
@Autowired
private ThemeRepository themeRepository;
@Autowired
private PaymentRepository paymentRepository;
@Autowired
private CanceledPaymentRepository canceledPaymentRepository;
every {
paymentService.savePayment(paymentApproveResponse, reservationEntity)
} returns paymentEntity.toReservationPaymentResponse()
@Test
@DisplayName("예약과 결제 정보를 추가한다.")
void addReservationWithPayment() {
// given
PaymentApprove.Response paymentInfo = new PaymentApprove.Response("payment-key", "order-id",
OffsetDateTime.now(), 10000L);
LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0);
LocalDate date = localDateTime.toLocalDate();
ReservationTimeEntity time = reservationTimeRepository.save(
new ReservationTimeEntity(null, localDateTime.toLocalTime()));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "member", "email@email.com", "password", Role.MEMBER));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "name", "desc", "thumbnail"));
ReservationRequest reservationRequest = new ReservationRequest(date, time.getId(), theme.getId(), "payment-key",
"order-id", 10000L, "NORMAL");
val result: ReservationResponse = reservationWithPaymentService.addReservationWithPayment(
request = reservationRequest,
paymentInfo = paymentApproveResponse,
memberId = memberId
)
// when
ReservationResponse reservationResponse = reservationWithPaymentService.addReservationWithPayment(
reservationRequest, paymentInfo, member.getId());
// then
reservationRepository.findById(reservationResponse.id)
.ifPresent(reservation -> {
assertThat(reservation.getMember().getId()).isEqualTo(member.getId());
assertThat(reservation.getTheme().getId()).isEqualTo(theme.getId());
assertThat(reservation.getDate()).isEqualTo(date);
assertThat(reservation.getReservationTime().getId()).isEqualTo(time.getId());
assertThat(reservation.getReservationStatus()).isEqualTo(ReservationStatus.CONFIRMED);
});
PaymentEntity payment = paymentRepository.findByPaymentKey("payment-key");
assertThat(payment).isNotNull();
assertThat(payment.getReservation().getId()).isEqualTo(reservationResponse.id);
assertThat(payment.getPaymentKey()).isEqualTo("payment-key");
assertThat(payment.getOrderId()).isEqualTo("order-id");
assertThat(payment.getTotalAmount()).isEqualTo(10000L);
assertSoftly(result) {
this.id shouldBe reservationEntity.id
this.date shouldBe reservationEntity.date
this.member.id shouldBe reservationEntity.member.id
this.time.id shouldBe reservationEntity.reservationTime.id
this.theme.id shouldBe reservationEntity.theme.id
this.status shouldBe ReservationStatus.CONFIRMED
}
}
@Test
@DisplayName("예약 ID를 이용하여 예약과 결제 정보를 제거하고, 결제 취소 정보를 저장한다.")
void removeReservationWithPayment() {
// given
PaymentApprove.Response paymentInfo = new PaymentApprove.Response("payment-key", "order-id",
OffsetDateTime.now(), 10000L);
LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0);
LocalDate date = localDateTime.toLocalDate();
ReservationTimeEntity time = reservationTimeRepository.save(
new ReservationTimeEntity(null, localDateTime.toLocalTime()));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "member", "admin@email.com", "password", Role.ADMIN));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "name", "desc", "thumbnail"));
ReservationRequest reservationRequest = new ReservationRequest(date, time.getId(), theme.getId(), "payment-key",
"order-id", 10000L, "NORMAL");
ReservationResponse reservationResponse = reservationWithPaymentService.addReservationWithPayment(
reservationRequest, paymentInfo, member.getId());
context("removeReservationWithPayment") {
test("예약 및 결제 정보를 삭제하고, 결제 취소 정보를 저장한다.") {
val paymentCancelRequest: PaymentCancel.Request = PaymentFixture.createCancelRequest().copy(
paymentKey = paymentEntity.paymentKey,
amount = paymentEntity.totalAmount,
cancelReason = "고객 요청"
)
// when
PaymentCancel.Request paymentCancelRequest = reservationWithPaymentService.removeReservationWithPayment(
reservationResponse.id, member.getId());
every {
paymentService.cancelPaymentByAdmin(reservationEntity.id!!)
} returns paymentCancelRequest
// then
assertThat(paymentCancelRequest.cancelReason).isEqualTo("고객 요청");
assertThat(reservationRepository.findById(reservationResponse.id)).isEmpty();
assertThat(paymentRepository.findByPaymentKey("payment-key")).isNull();
assertThat(canceledPaymentRepository.findByPaymentKey("payment-key")).isNotNull();
every {
reservationService.removeReservationById(reservationEntity.id!!, reservationEntity.member.id!!)
} just Runs
val result: PaymentCancel.Request = reservationWithPaymentService.removeReservationWithPayment(
reservationId = reservationEntity.id!!,
memberId = reservationEntity.member.id!!
)
result shouldBe paymentCancelRequest
}
}
@Test
@DisplayName("결제 정보가 없으면 True를 반환한다.")
void isNotPaidReservation() {
// given
PaymentApprove.Response paymentInfo = new PaymentApprove.Response("payment-key", "order-id",
OffsetDateTime.now(), 10000L);
LocalDateTime localDateTime = LocalDateTime.now().plusHours(1L);
LocalDate date = localDateTime.toLocalDate();
ReservationTimeEntity time = reservationTimeRepository.save(
new ReservationTimeEntity(null, localDateTime.toLocalTime()));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "member", "admin@email.com", "password", Role.ADMIN));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "name", "desc", "thumbnail"));
context("isNotPaidReservation") {
test("결제된 예약이면 true를 반환한다.") {
every {
paymentService.isReservationPaid(reservationEntity.id!!)
} returns false
ReservationEntity saved = reservationRepository.save(
new ReservationEntity(null, date, time, theme, member, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED));
val result: Boolean = reservationWithPaymentService.isNotPaidReservation(reservationEntity.id!!)
// when
boolean result = reservationWithPaymentService.isNotPaidReservation(saved.getId());
// then
assertThat(result).isTrue();
result shouldBe true
}
@Test
@DisplayName("결제 정보가 있으면 False를 반환한다.")
void isPaidReservation() {
// given
PaymentApprove.Response paymentInfo = new PaymentApprove.Response("payment-key", "order-id",
OffsetDateTime.now(), 10000L);
LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0);
LocalDate date = localDateTime.toLocalDate();
ReservationTimeEntity time = reservationTimeRepository.save(
new ReservationTimeEntity(null, localDateTime.toLocalTime()));
MemberEntity member = memberRepository.save(
new MemberEntity(null, "member", "admin@email.com", "password", Role.ADMIN));
ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "name", "desc", "thumbnail"));
ReservationRequest reservationRequest = new ReservationRequest(date, time.getId(), theme.getId(), "payment-key",
"order-id", 10000L, "NORMAL");
test("결제되지 않은 예약이면 false를 반환한다.") {
every {
paymentService.isReservationPaid(reservationEntity.id!!)
} returns true
ReservationResponse reservationResponse = reservationWithPaymentService.addReservationWithPayment(
reservationRequest, paymentInfo, member.getId());
val result: Boolean = reservationWithPaymentService.isNotPaidReservation(reservationEntity.id!!)
// when
boolean result = reservationWithPaymentService.isNotPaidReservation(reservationResponse.id);
// then
assertThat(result).isFalse();
result shouldBe false
}
}
}
}
})

View File

@ -161,7 +161,7 @@ class ReservationRepositoryTest(
test("결제 정보를 포함한 회원의 예약 목록을 반환한다.") {
val payment: PaymentEntity = PaymentFixture.create(
reservationId = reservation.id!!
reservation = reservation
).also {
entityManager.persist(it)
entityManager.flush()

View File

@ -128,14 +128,14 @@ object PaymentFixture {
orderId: String = ORDER_ID,
paymentKey: String = PAYMENT_KEY,
totalAmount: Long = AMOUNT,
reservationId: Long = Random.nextLong(),
reservation: ReservationEntity = ReservationFixture.create(id = 1L),
approvedAt: OffsetDateTime = OffsetDateTime.now()
): PaymentEntity = PaymentEntity(
id = id,
orderId = orderId,
paymentKey = paymentKey,
totalAmount = totalAmount,
reservation = ReservationFixture.create(id = reservationId),
reservation = reservation,
approvedAt = approvedAt
)