[#16] Reservation 도메인 코드 코틀린 마이그레이션 #17

Merged
pricelees merged 40 commits from refactor/#16 into main 2025-07-21 12:08:56 +00:00
7 changed files with 170 additions and 215 deletions
Showing only changes of commit 5ddfd02572 - Show all commits

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer import roomescape.payment.infrastructure.client.PaymentCancelResponseDeserializer
import roomescape.payment.infrastructure.persistence.PaymentEntity import roomescape.payment.infrastructure.persistence.PaymentEntity
import roomescape.reservation.web.ReservationResponse import roomescape.reservation.web.ReservationResponse
import roomescape.reservation.web.toResponse
import java.time.OffsetDateTime import java.time.OffsetDateTime
class PaymentApprove { class PaymentApprove {
@ -60,6 +61,6 @@ fun PaymentEntity.toReservationPaymentResponse(): ReservationPaymentResponse = R
orderId = this.orderId, orderId = this.orderId,
paymentKey = this.paymentKey, paymentKey = this.paymentKey,
totalAmount = this.totalAmount, totalAmount = this.totalAmount,
reservation = ReservationResponse.from(this.reservation), reservation = this.reservation.toResponse(),
approvedAt = this.approvedAt 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 org.springframework.stereotype.Service; import roomescape.payment.business.PaymentService
import org.springframework.transaction.annotation.Transactional; import roomescape.payment.web.PaymentApprove
import roomescape.payment.web.PaymentCancel
import roomescape.payment.business.PaymentService; import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.payment.web.PaymentApprove; import roomescape.reservation.web.ReservationRequest
import roomescape.payment.web.PaymentCancel; import roomescape.reservation.web.ReservationResponse
import roomescape.payment.web.ReservationPaymentResponse; import java.time.OffsetDateTime
import roomescape.reservation.infrastructure.persistence.ReservationEntity;
import roomescape.reservation.web.ReservationRequest;
import roomescape.reservation.web.ReservationResponse;
@Service @Service
@Transactional @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; return paymentService.savePayment(paymentInfo, reservation)
private final PaymentService paymentService; .reservation
}
public ReservationWithPaymentService(ReservationService reservationService, fun saveCanceledPayment(
PaymentService paymentService) { cancelInfo: PaymentCancel.Response,
this.reservationService = reservationService; approvedAt: OffsetDateTime,
this.paymentService = paymentService; paymentKey: String
} ) {
paymentService.saveCanceledPayment(cancelInfo, approvedAt, paymentKey)
}
public ReservationResponse addReservationWithPayment(ReservationRequest request, fun removeReservationWithPayment(
PaymentApprove.Response paymentInfo, reservationId: Long,
Long memberId) { memberId: Long
ReservationEntity reservation = reservationService.addReservation(request, memberId); ): PaymentCancel.Request {
ReservationPaymentResponse reservationPaymentResponse = paymentService.savePayment(paymentInfo, reservation); val paymentCancelRequest = paymentService.cancelPaymentByAdmin(reservationId)
reservationService.removeReservationById(reservationId, memberId)
return reservationPaymentResponse.reservation(); return paymentCancelRequest
} }
public void saveCanceledPayment(PaymentCancel.Response cancelInfo, OffsetDateTime approvedAt, String paymentKey) { @Transactional(readOnly = true)
paymentService.saveCanceledPayment(cancelInfo, approvedAt, paymentKey); fun isNotPaidReservation(reservationId: Long): Boolean = !paymentService.isReservationPaid(reservationId)
}
public PaymentCancel.Request removeReservationWithPayment(Long reservationId, Long memberId) {
PaymentCancel.Request paymentCancelRequest = paymentService.cancelPaymentByAdmin(reservationId);
reservationService.removeReservationById(reservationId, memberId);
return paymentCancelRequest;
}
@Transactional(readOnly = true) fun updateCanceledTime(
public boolean isNotPaidReservation(Long reservationId) { paymentKey: String,
return !paymentService.isReservationPaid(reservationId); canceledAt: OffsetDateTime
} ) {
paymentService.updateCanceledTime(paymentKey, canceledAt)
public void updateCanceledTime(String paymentKey, OffsetDateTime canceledAt) { }
paymentService.updateCanceledTime(paymentKey, canceledAt);
}
} }

View File

@ -3,7 +3,6 @@ package roomescape.reservation.web
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.media.Schema
import roomescape.member.web.MemberResponse import roomescape.member.web.MemberResponse
import roomescape.member.web.MemberResponse.Companion.fromEntity
import roomescape.member.web.toResponse import roomescape.member.web.toResponse
import roomescape.reservation.infrastructure.persistence.ReservationEntity import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.reservation.infrastructure.persistence.ReservationStatus import roomescape.reservation.infrastructure.persistence.ReservationStatus
@ -78,9 +77,9 @@ data class ReservationResponse(
return ReservationResponse( return ReservationResponse(
reservation.id!!, reservation.id!!,
reservation.date, reservation.date,
fromEntity(reservation.member), reservation.member.toResponse(),
ReservationTimeResponse.Companion.from(reservation.reservationTime), reservation.reservationTime.toResponse(),
ThemeResponse.Companion.from(reservation.theme), reservation.theme.toResponse(),
reservation.reservationStatus reservation.reservationStatus
) )
} }

View File

@ -6,6 +6,7 @@ import io.kotest.matchers.shouldBe
import jakarta.persistence.EntityManager import jakarta.persistence.EntityManager
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import roomescape.reservation.infrastructure.persistence.ReservationEntity
import roomescape.util.PaymentFixture import roomescape.util.PaymentFixture
import roomescape.util.ReservationFixture import roomescape.util.ReservationFixture
@ -15,23 +16,23 @@ class PaymentRepositoryTest(
@Autowired val entityManager: EntityManager @Autowired val entityManager: EntityManager
) : FunSpec() { ) : FunSpec() {
var reservationId: Long = 0L lateinit var reservation: ReservationEntity
init { init {
context("existsByReservationId") { context("existsByReservationId") {
beforeTest { beforeTest {
reservationId = setupReservation() reservation = setupReservation()
PaymentFixture.create(reservationId = reservationId) PaymentFixture.create(reservation = reservation)
.also { paymentRepository.save(it) } .also { paymentRepository.save(it) }
} }
test("true") { test("true") {
paymentRepository.existsByReservationId(reservationId) paymentRepository.existsByReservationId(reservation.id!!)
.also { it shouldBe true } .also { it shouldBe true }
} }
test("false") { test("false") {
paymentRepository.existsByReservationId(reservationId + 1) paymentRepository.existsByReservationId(reservation.id!! + 1L)
.also { it shouldBe false } .also { it shouldBe false }
} }
} }
@ -40,20 +41,20 @@ class PaymentRepositoryTest(
lateinit var paymentKey: String lateinit var paymentKey: String
beforeTest { beforeTest {
reservationId = setupReservation() reservation = setupReservation()
paymentKey = PaymentFixture.create(reservationId = reservationId) paymentKey = PaymentFixture.create(reservation = reservation)
.also { paymentRepository.save(it) } .also { paymentRepository.save(it) }
.paymentKey .paymentKey
} }
test("정상 반환") { test("정상 반환") {
paymentRepository.findPaymentKeyByReservationId(reservationId) paymentRepository.findPaymentKeyByReservationId(reservation.id!!)
?.let { it shouldBe paymentKey } ?.let { it shouldBe paymentKey }
?: throw AssertionError("Unexpected null value") ?: throw AssertionError("Unexpected null value")
} }
test("null 반환") { test("null 반환") {
paymentRepository.findPaymentKeyByReservationId(reservationId + 1) paymentRepository.findPaymentKeyByReservationId(reservation.id!! + 1)
.also { it shouldBe null } .also { it shouldBe null }
} }
} }
@ -62,8 +63,8 @@ class PaymentRepositoryTest(
lateinit var payment: PaymentEntity lateinit var payment: PaymentEntity
beforeTest { beforeTest {
reservationId = setupReservation() reservation = setupReservation()
payment = PaymentFixture.create(reservationId = reservationId) payment = PaymentFixture.create(reservation = reservation)
.also { paymentRepository.save(it) } .also { paymentRepository.save(it) }
} }
@ -89,7 +90,7 @@ class PaymentRepositoryTest(
} }
} }
private fun setupReservation(): Long { private fun setupReservation(): ReservationEntity {
return ReservationFixture.create().also { return ReservationFixture.create().also {
entityManager.persist(it.member) entityManager.persist(it.member)
entityManager.persist(it.theme) entityManager.persist(it.theme)
@ -98,6 +99,6 @@ class PaymentRepositoryTest(
entityManager.flush() entityManager.flush()
entityManager.clear() 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; class ReservationWithPaymentServiceTest : FunSpec({
import java.time.LocalDateTime; val reservationService: ReservationService = mockk()
import java.time.OffsetDateTime; val paymentService: PaymentService = mockk()
import org.junit.jupiter.api.DisplayName; val reservationWithPaymentService = ReservationWithPaymentService(
import org.junit.jupiter.api.Test; reservationService = reservationService,
import org.springframework.beans.factory.annotation.Autowired; paymentService = paymentService
import org.springframework.boot.test.context.SpringBootTest; )
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import roomescape.member.infrastructure.persistence.MemberEntity; val reservationRequest: ReservationRequest = ReservationFixture.createRequest()
import roomescape.member.infrastructure.persistence.MemberRepository; val paymentApproveResponse = PaymentFixture.createApproveResponse()
import roomescape.member.infrastructure.persistence.Role; val memberId = 1L
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository; val reservationEntity: ReservationEntity = ReservationFixture.create(
import roomescape.payment.infrastructure.persistence.PaymentEntity; id = 1L,
import roomescape.payment.infrastructure.persistence.PaymentRepository; date = reservationRequest.date,
import roomescape.payment.web.PaymentApprove; reservationTime = ReservationTimeFixture.create(id = reservationRequest.timeId),
import roomescape.payment.web.PaymentCancel; theme = ThemeFixture.create(id = reservationRequest.themeId),
import roomescape.reservation.infrastructure.persistence.ReservationEntity; member = MemberFixture.create(id = memberId),
import roomescape.reservation.infrastructure.persistence.ReservationRepository; status = ReservationStatus.CONFIRMED
import roomescape.reservation.infrastructure.persistence.ReservationStatus; )
import roomescape.reservation.infrastructure.persistence.ReservationTimeEntity; val paymentEntity: PaymentEntity = PaymentFixture.create(
import roomescape.reservation.infrastructure.persistence.ReservationTimeRepository; id = 1L,
import roomescape.reservation.web.ReservationRequest; orderId = reservationRequest.orderId,
import roomescape.reservation.web.ReservationResponse; paymentKey = reservationRequest.paymentKey,
import roomescape.theme.infrastructure.persistence.ThemeEntity; totalAmount = reservationRequest.amount,
import roomescape.theme.infrastructure.persistence.ThemeRepository; reservation = reservationEntity,
)
@SpringBootTest context("addReservationWithPayment") {
@Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) test("예약 및 결제 정보를 저장한다.") {
class ReservationWithPaymentServiceTest { every {
reservationService.addReservation(reservationRequest, memberId)
} returns reservationEntity
@Autowired every {
private ReservationWithPaymentService reservationWithPaymentService; paymentService.savePayment(paymentApproveResponse, reservationEntity)
@Autowired } returns paymentEntity.toReservationPaymentResponse()
private ReservationRepository reservationRepository;
@Autowired
private MemberRepository memberRepository;
@Autowired
private ReservationTimeRepository reservationTimeRepository;
@Autowired
private ThemeRepository themeRepository;
@Autowired
private PaymentRepository paymentRepository;
@Autowired
private CanceledPaymentRepository canceledPaymentRepository;
@Test val result: ReservationResponse = reservationWithPaymentService.addReservationWithPayment(
@DisplayName("예약과 결제 정보를 추가한다.") request = reservationRequest,
void addReservationWithPayment() { paymentInfo = paymentApproveResponse,
// given memberId = memberId
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");
// when assertSoftly(result) {
ReservationResponse reservationResponse = reservationWithPaymentService.addReservationWithPayment( this.id shouldBe reservationEntity.id
reservationRequest, paymentInfo, member.getId()); 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
}
}
// 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"); context("removeReservationWithPayment") {
assertThat(payment).isNotNull(); test("예약 및 결제 정보를 삭제하고, 결제 취소 정보를 저장한다.") {
assertThat(payment.getReservation().getId()).isEqualTo(reservationResponse.id); val paymentCancelRequest: PaymentCancel.Request = PaymentFixture.createCancelRequest().copy(
assertThat(payment.getPaymentKey()).isEqualTo("payment-key"); paymentKey = paymentEntity.paymentKey,
assertThat(payment.getOrderId()).isEqualTo("order-id"); amount = paymentEntity.totalAmount,
assertThat(payment.getTotalAmount()).isEqualTo(10000L); cancelReason = "고객 요청"
} )
@Test every {
@DisplayName("예약 ID를 이용하여 예약과 결제 정보를 제거하고, 결제 취소 정보를 저장한다.") paymentService.cancelPaymentByAdmin(reservationEntity.id!!)
void removeReservationWithPayment() { } returns paymentCancelRequest
// 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( every {
reservationRequest, paymentInfo, member.getId()); reservationService.removeReservationById(reservationEntity.id!!, reservationEntity.member.id!!)
} just Runs
// when val result: PaymentCancel.Request = reservationWithPaymentService.removeReservationWithPayment(
PaymentCancel.Request paymentCancelRequest = reservationWithPaymentService.removeReservationWithPayment( reservationId = reservationEntity.id!!,
reservationResponse.id, member.getId()); memberId = reservationEntity.member.id!!
)
// then result shouldBe paymentCancelRequest
assertThat(paymentCancelRequest.cancelReason).isEqualTo("고객 요청"); }
assertThat(reservationRepository.findById(reservationResponse.id)).isEmpty(); }
assertThat(paymentRepository.findByPaymentKey("payment-key")).isNull();
assertThat(canceledPaymentRepository.findByPaymentKey("payment-key")).isNotNull();
}
@Test context("isNotPaidReservation") {
@DisplayName("결제 정보가 없으면 True를 반환한다.") test("결제된 예약이면 true를 반환한다.") {
void isNotPaidReservation() { every {
// given paymentService.isReservationPaid(reservationEntity.id!!)
PaymentApprove.Response paymentInfo = new PaymentApprove.Response("payment-key", "order-id", } returns false
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"));
ReservationEntity saved = reservationRepository.save( val result: Boolean = reservationWithPaymentService.isNotPaidReservation(reservationEntity.id!!)
new ReservationEntity(null, date, time, theme, member, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED));
// when result shouldBe true
boolean result = reservationWithPaymentService.isNotPaidReservation(saved.getId()); }
// then test("결제되지 않은 예약이면 false를 반환한다.") {
assertThat(result).isTrue(); every {
} paymentService.isReservationPaid(reservationEntity.id!!)
} returns true
@Test val result: Boolean = reservationWithPaymentService.isNotPaidReservation(reservationEntity.id!!)
@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");
ReservationResponse reservationResponse = reservationWithPaymentService.addReservationWithPayment( result shouldBe false
reservationRequest, paymentInfo, member.getId()); }
}
// when }
boolean result = reservationWithPaymentService.isNotPaidReservation(reservationResponse.id); })
// then
assertThat(result).isFalse();
}
}

View File

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

View File

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