[#20] 도메인별 예외 분리 #21

Merged
pricelees merged 37 commits from refactor/#20 into main 2025-07-24 02:48:53 +00:00
3 changed files with 48 additions and 39 deletions
Showing only changes of commit ac7f9fa330 - Show all commits

View File

@ -39,7 +39,7 @@ class ReservationRepositoryTest(
} }
test("입력된 시간과 일치하는 예약을 반환한다.") { test("입력된 시간과 일치하는 예약을 반환한다.") {
assertSoftly(reservationRepository.findByTime(time)) { assertSoftly(reservationRepository.findAllByTime(time)) {
it shouldHaveSize 1 it shouldHaveSize 1
assertSoftly(it.first().time.startAt) { result -> assertSoftly(it.first().time.startAt) { result ->
result.hour shouldBe time.startAt.hour result.hour shouldBe time.startAt.hour

View File

@ -6,10 +6,9 @@ import io.kotest.matchers.shouldBe
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.reservation.infrastructure.persistence.ReservationRepository import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.time.exception.TimeErrorCode
import roomescape.time.exception.TimeException
import roomescape.time.infrastructure.persistence.TimeRepository import roomescape.time.infrastructure.persistence.TimeRepository
import roomescape.time.web.TimeCreateRequest import roomescape.time.web.TimeCreateRequest
import roomescape.util.TimeFixture import roomescape.util.TimeFixture
@ -25,63 +24,70 @@ class TimeServiceTest : FunSpec({
) )
context("findTimeById") { context("findTimeById") {
test("시간을 찾을 수 없으면 400 에러를 던진다.") { test("시간을 찾을 수 없으면 예외 응답") {
val id = 1L val id = 1L
every { timeRepository.findByIdOrNull(id) } returns null every { timeRepository.findByIdOrNull(id) } returns null
shouldThrow<RoomescapeException> { shouldThrow<TimeException> {
timeService.findById(id) timeService.findById(id)
}.apply { }.also {
errorType shouldBe ErrorType.TIME_NOT_FOUND it.errorCode shouldBe TimeErrorCode.TIME_NOT_FOUND
httpStatus shouldBe HttpStatus.BAD_REQUEST
} }
} }
} }
context("addTime") { context("createTime") {
test("중복된 시간이 있으면 409 에러를 던진다.") { val request = TimeCreateRequest(startAt = LocalTime.of(10, 0))
val request = TimeCreateRequest(startAt = LocalTime.of(10, 0))
test("정상 저장") {
every { timeRepository.existsByStartAt(request.startAt) } returns false
every { timeRepository.save(any()) } returns TimeFixture.create(
id = 1L,
startAt = request.startAt
)
val response = timeService.createTime(request)
response.id shouldBe 1L
}
test("중복된 시간이 있으면 예외 응답") {
every { timeRepository.existsByStartAt(request.startAt) } returns true every { timeRepository.existsByStartAt(request.startAt) } returns true
shouldThrow<RoomescapeException> { shouldThrow<TimeException> {
timeService.createTime(request) timeService.createTime(request)
}.apply { }.also {
errorType shouldBe ErrorType.TIME_DUPLICATED it.errorCode shouldBe TimeErrorCode.TIME_DUPLICATED
httpStatus shouldBe HttpStatus.CONFLICT
} }
} }
} }
context("removeTimeById") { context("removeTimeById") {
test("시간을 찾을 수 없으면 400 에러를 던진다.") { test("시간을 찾을 수 없으면 예외 응답") {
val id = 1L val id = 1L
every { timeRepository.findByIdOrNull(id) } returns null every { timeRepository.findByIdOrNull(id) } returns null
shouldThrow<RoomescapeException> { shouldThrow<TimeException> {
timeService.deleteTime(id) timeService.deleteTime(id)
}.apply { }.also {
errorType shouldBe ErrorType.TIME_NOT_FOUND it.errorCode shouldBe TimeErrorCode.TIME_NOT_FOUND
httpStatus shouldBe HttpStatus.BAD_REQUEST
} }
} }
test("예약이 있는 시간이면 409 에러를 던진다.") { test("예약이 있는 시간이면 예외 응답") {
val id = 1L val id = 1L
val time = TimeFixture.create() val time = TimeFixture.create()
every { timeRepository.findByIdOrNull(id) } returns time every { timeRepository.findByIdOrNull(id) } returns time
every { reservationRepository.findByTime(time) } returns listOf(mockk()) every { reservationRepository.findAllByTime(time) } returns listOf(mockk())
shouldThrow<RoomescapeException> { shouldThrow<TimeException> {
timeService.deleteTime(id) timeService.deleteTime(id)
}.apply { }.also {
errorType shouldBe ErrorType.TIME_IS_USED_CONFLICT it.errorCode shouldBe TimeErrorCode.TIME_ALREADY_RESERVED
httpStatus shouldBe HttpStatus.CONFLICT
} }
} }
} }
}) })

View File

@ -12,9 +12,9 @@ import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.MediaType 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.reservation.infrastructure.persistence.ReservationRepository import roomescape.reservation.infrastructure.persistence.ReservationRepository
import roomescape.time.business.TimeService import roomescape.time.business.TimeService
import roomescape.time.exception.TimeErrorCode
import roomescape.time.infrastructure.persistence.TimeEntity import roomescape.time.infrastructure.persistence.TimeEntity
import roomescape.time.infrastructure.persistence.TimeRepository import roomescape.time.infrastructure.persistence.TimeRepository
import roomescape.util.ReservationFixture import roomescape.util.ReservationFixture
@ -129,7 +129,8 @@ class TimeControllerTest(
} }
} }
Then("동일한 시간이 존재하면 409 응답") { Then("동일한 시간이 존재하면 예외 응답") {
val expectedError = TimeErrorCode.TIME_DUPLICATED
every { every {
timeRepository.existsByStartAt(time) timeRepository.existsByStartAt(time)
} returns true } returns true
@ -139,10 +140,10 @@ class TimeControllerTest(
endpoint = endpoint, endpoint = endpoint,
body = request, body = request,
) { ) {
status { isConflict() } status { isEqualTo(expectedError.httpStatus.value()) }
content { content {
contentType(MediaType.APPLICATION_JSON) contentType(MediaType.APPLICATION_JSON)
jsonPath("$.errorType") { value(ErrorType.TIME_DUPLICATED.name) } jsonPath("$.code") { value(expectedError.errorCode) }
} }
} }
} }
@ -185,8 +186,9 @@ class TimeControllerTest(
} }
} }
Then("없는 시간을 조회하면 400 응답") { Then("없는 시간을 조회하면 예외 응답") {
val id = 1L val id = 1L
val expectedError = TimeErrorCode.TIME_NOT_FOUND
every { every {
timeRepository.findByIdOrNull(id) timeRepository.findByIdOrNull(id)
} returns null } returns null
@ -195,32 +197,33 @@ class TimeControllerTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = "/times/$id", endpoint = "/times/$id",
) { ) {
status { isBadRequest() } status { isEqualTo(expectedError.httpStatus.value()) }
content { content {
contentType(MediaType.APPLICATION_JSON) contentType(MediaType.APPLICATION_JSON)
jsonPath("$.errorType") { value(ErrorType.TIME_NOT_FOUND.name) } jsonPath("$.code") { value(expectedError.errorCode) }
} }
} }
} }
Then("예약이 있는 시간을 삭제하면 409 응답") { Then("예약이 있는 시간을 삭제하면 예외 응답") {
val id = 1L val id = 1L
val expectedError = TimeErrorCode.TIME_ALREADY_RESERVED
every { every {
timeRepository.findByIdOrNull(id) timeRepository.findByIdOrNull(id)
} returns TimeFixture.create(id = id) } returns TimeFixture.create(id = id)
every { every {
reservationRepository.findByTime(any()) reservationRepository.findAllByTime(any())
} returns listOf(ReservationFixture.create()) } returns listOf(ReservationFixture.create())
runDeleteTest( runDeleteTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = "/times/$id", endpoint = "/times/$id",
) { ) {
status { isConflict() } status { isEqualTo(expectedError.httpStatus.value()) }
content { content {
contentType(MediaType.APPLICATION_JSON) contentType(MediaType.APPLICATION_JSON)
jsonPath("$.errorType") { value(ErrorType.TIME_IS_USED_CONFLICT.name) } jsonPath("$.code") { value(expectedError.errorCode) }
} }
} }
} }