generated from pricelees/issue-pr-template
[#30] 코드 구조 개선 #31
@ -16,4 +16,7 @@ class TimeWithAvailability(
|
|||||||
startAt = startAt,
|
startAt = startAt,
|
||||||
isAvailable = isReservable
|
isAvailable = isReservable
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// for test
|
||||||
|
fun canReserve(): Boolean = isReservable
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,38 +1,42 @@
|
|||||||
package roomescape.time.business
|
package roomescape.time.business
|
||||||
|
|
||||||
|
import io.kotest.assertions.assertSoftly
|
||||||
import io.kotest.assertions.throwables.shouldNotThrow
|
import io.kotest.assertions.throwables.shouldNotThrow
|
||||||
import io.kotest.assertions.throwables.shouldThrow
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
import io.kotest.core.spec.style.FunSpec
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.collections.shouldContainExactly
|
||||||
|
import io.kotest.matchers.collections.shouldHaveSize
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import roomescape.theme.business.domain.TimeWithAvailability
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
import roomescape.theme.exception.ThemeErrorCode
|
||||||
|
import roomescape.theme.exception.ThemeException
|
||||||
import roomescape.time.exception.TimeErrorCode
|
import roomescape.time.exception.TimeErrorCode
|
||||||
import roomescape.time.exception.TimeException
|
import roomescape.time.exception.TimeException
|
||||||
import roomescape.time.infrastructure.persistence.TimeRepository
|
import roomescape.time.implement.TimeFinder
|
||||||
|
import roomescape.time.implement.TimeWriter
|
||||||
|
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||||
import roomescape.time.web.TimeCreateRequest
|
import roomescape.time.web.TimeCreateRequest
|
||||||
import roomescape.util.TsidFactory
|
|
||||||
import roomescape.util.TimeFixture
|
import roomescape.util.TimeFixture
|
||||||
|
import java.time.LocalDate
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
class TimeServiceTest : FunSpec({
|
class TimeServiceTest : FunSpec({
|
||||||
val timeRepository: TimeRepository = mockk()
|
val timeFinder: TimeFinder = mockk()
|
||||||
val reservationRepository: ReservationRepository = mockk()
|
val timeWriter: TimeWriter = mockk()
|
||||||
|
|
||||||
val timeService = TimeService(
|
val timeService = TimeService(timeFinder, timeWriter)
|
||||||
tsidFactory = TsidFactory,
|
|
||||||
timeRepository = timeRepository,
|
|
||||||
reservationRepository = reservationRepository
|
|
||||||
)
|
|
||||||
|
|
||||||
context("findTimeById") {
|
context("findById") {
|
||||||
test("시간을 찾을 수 없으면 예외 응답") {
|
test("시간을 찾을 수 없으면 예외 응답") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
|
|
||||||
every { timeRepository.findByIdOrNull(id) } returns null
|
every {
|
||||||
|
timeFinder.findById(id)
|
||||||
|
} throws TimeException(TimeErrorCode.TIME_NOT_FOUND)
|
||||||
|
|
||||||
shouldThrow<TimeException> {
|
shouldThrow<TimeException> {
|
||||||
timeService.findById(id)
|
timeService.findById(id)
|
||||||
@ -42,22 +46,81 @@ class TimeServiceTest : FunSpec({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context("findTimes") {
|
||||||
|
test("정상 응답") {
|
||||||
|
val times: List<TimeEntity> = listOf(
|
||||||
|
TimeFixture.create(startAt = LocalTime.now()),
|
||||||
|
TimeFixture.create(startAt = LocalTime.now().plusMinutes(1)),
|
||||||
|
TimeFixture.create(startAt = LocalTime.now().plusMinutes(2))
|
||||||
|
)
|
||||||
|
|
||||||
|
every {
|
||||||
|
timeFinder.findAll()
|
||||||
|
} returns times
|
||||||
|
|
||||||
|
val response = timeService.findTimes()
|
||||||
|
|
||||||
|
assertSoftly(response.times) {
|
||||||
|
it shouldHaveSize times.size
|
||||||
|
it.map { time -> time.startAt } shouldContainExactly times.map { time -> time.startAt }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context("findTimesWithAvailability") {
|
||||||
|
val date = LocalDate.now()
|
||||||
|
val themeId = 1L
|
||||||
|
|
||||||
|
test("정상 응답") {
|
||||||
|
val times: List<TimeWithAvailability> = listOf(
|
||||||
|
TimeWithAvailability(1, LocalTime.now(), date, themeId, true),
|
||||||
|
TimeWithAvailability(2, LocalTime.now().plusMinutes(1), date, themeId, false),
|
||||||
|
TimeWithAvailability(3, LocalTime.now().plusMinutes(2), date, themeId, true)
|
||||||
|
)
|
||||||
|
|
||||||
|
every {
|
||||||
|
timeFinder.findAllWithAvailabilityByDateAndThemeId(date, themeId)
|
||||||
|
} returns times
|
||||||
|
|
||||||
|
val response = timeService.findTimesWithAvailability(date, themeId)
|
||||||
|
|
||||||
|
assertSoftly(response.times) {
|
||||||
|
it shouldHaveSize times.size
|
||||||
|
it.map { time -> time.isAvailable } shouldContainExactly times.map { time -> time.canReserve() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("테마를 찾을 수 없으면 예외 응답") {
|
||||||
|
every {
|
||||||
|
timeFinder.findAllWithAvailabilityByDateAndThemeId(date, themeId)
|
||||||
|
} throws ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
||||||
|
|
||||||
|
shouldThrow<ThemeException> {
|
||||||
|
timeService.findTimesWithAvailability(date, themeId)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe ThemeErrorCode.THEME_NOT_FOUND
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context("createTime") {
|
context("createTime") {
|
||||||
val request = TimeCreateRequest(startAt = LocalTime.of(10, 0))
|
val request = TimeCreateRequest(startAt = LocalTime.of(10, 0))
|
||||||
|
|
||||||
test("정상 저장") {
|
test("정상 저장") {
|
||||||
every { timeRepository.existsByStartAt(request.startAt) } returns false
|
val time: TimeEntity = TimeFixture.create(startAt = request.startAt)
|
||||||
every { timeRepository.save(any()) } returns TimeFixture.create(
|
|
||||||
id = 1L,
|
every {
|
||||||
startAt = request.startAt
|
timeWriter.create(request.startAt)
|
||||||
)
|
} returns time
|
||||||
|
|
||||||
val response = timeService.createTime(request)
|
val response = timeService.createTime(request)
|
||||||
response.id shouldBe 1L
|
response.id shouldBe time.id
|
||||||
}
|
}
|
||||||
|
|
||||||
test("중복된 시간이 있으면 예외 응답") {
|
test("중복된 시간이 있으면 예외 응답") {
|
||||||
every { timeRepository.existsByStartAt(request.startAt) } returns true
|
every {
|
||||||
|
timeWriter.create(request.startAt)
|
||||||
|
} throws TimeException(TimeErrorCode.TIME_DUPLICATED)
|
||||||
|
|
||||||
shouldThrow<TimeException> {
|
shouldThrow<TimeException> {
|
||||||
timeService.createTime(request)
|
timeService.createTime(request)
|
||||||
@ -67,14 +130,13 @@ class TimeServiceTest : FunSpec({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context("removeTimeById") {
|
context("deleteTime") {
|
||||||
test("정상 제거 및 응답") {
|
test("정상 제거 및 응답") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
val time = TimeFixture.create(id = id)
|
val time = TimeFixture.create(id = id)
|
||||||
|
|
||||||
every { timeRepository.findByIdOrNull(id) } returns time
|
every { timeFinder.findById(id) } returns time
|
||||||
every { reservationRepository.findAllByTime(time) } returns emptyList()
|
every { timeWriter.delete(time) } just Runs
|
||||||
every { timeRepository.delete(time) } just Runs
|
|
||||||
|
|
||||||
shouldNotThrow<Exception> {
|
shouldNotThrow<Exception> {
|
||||||
timeService.deleteTime(id)
|
timeService.deleteTime(id)
|
||||||
@ -84,7 +146,7 @@ class TimeServiceTest : FunSpec({
|
|||||||
test("시간을 찾을 수 없으면 예외 응답") {
|
test("시간을 찾을 수 없으면 예외 응답") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
|
|
||||||
every { timeRepository.findByIdOrNull(id) } returns null
|
every { timeFinder.findById(id) } throws TimeException(TimeErrorCode.TIME_NOT_FOUND)
|
||||||
|
|
||||||
shouldThrow<TimeException> {
|
shouldThrow<TimeException> {
|
||||||
timeService.deleteTime(id)
|
timeService.deleteTime(id)
|
||||||
@ -97,9 +159,8 @@ class TimeServiceTest : FunSpec({
|
|||||||
val id = 1L
|
val id = 1L
|
||||||
val time = TimeFixture.create()
|
val time = TimeFixture.create()
|
||||||
|
|
||||||
every { timeRepository.findByIdOrNull(id) } returns time
|
every { timeFinder.findById(id) } returns time
|
||||||
|
every { timeWriter.delete(time) } throws TimeException(TimeErrorCode.TIME_ALREADY_RESERVED)
|
||||||
every { reservationRepository.findAllByTime(time) } returns listOf(mockk())
|
|
||||||
|
|
||||||
shouldThrow<TimeException> {
|
shouldThrow<TimeException> {
|
||||||
timeService.deleteTime(id)
|
timeService.deleteTime(id)
|
||||||
|
|||||||
109
src/test/kotlin/roomescape/time/implement/TimeFinderTest.kt
Normal file
109
src/test/kotlin/roomescape/time/implement/TimeFinderTest.kt
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package roomescape.time.implement
|
||||||
|
|
||||||
|
import io.kotest.assertions.assertSoftly
|
||||||
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.collections.shouldHaveSize
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
import roomescape.reservation.implement.ReservationFinder
|
||||||
|
import roomescape.theme.business.domain.TimeWithAvailability
|
||||||
|
import roomescape.theme.exception.ThemeErrorCode
|
||||||
|
import roomescape.theme.exception.ThemeException
|
||||||
|
import roomescape.theme.implement.ThemeFinder
|
||||||
|
import roomescape.time.exception.TimeErrorCode
|
||||||
|
import roomescape.time.exception.TimeException
|
||||||
|
import roomescape.time.infrastructure.persistence.TimeRepository
|
||||||
|
import roomescape.util.TimeFixture
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
|
class TimeFinderTest : FunSpec({
|
||||||
|
val timeRepository: TimeRepository = mockk()
|
||||||
|
val reservationFinder: ReservationFinder = mockk()
|
||||||
|
val themeFinder: ThemeFinder = mockk()
|
||||||
|
|
||||||
|
val timeFinder = TimeFinder(timeRepository, reservationFinder, themeFinder)
|
||||||
|
|
||||||
|
context("findAll") {
|
||||||
|
test("모든 시간을 조회한다.") {
|
||||||
|
every {
|
||||||
|
timeRepository.findAll()
|
||||||
|
} returns listOf(mockk(), mockk(), mockk())
|
||||||
|
|
||||||
|
timeRepository.findAll() shouldHaveSize 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context("findById") {
|
||||||
|
val timeId = 1L
|
||||||
|
test("동일한 ID인 시간을 찾아 응답한다.") {
|
||||||
|
every {
|
||||||
|
timeRepository.findByIdOrNull(timeId)
|
||||||
|
} returns mockk()
|
||||||
|
|
||||||
|
timeFinder.findById(timeId)
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
timeRepository.findByIdOrNull(timeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("동일한 ID인 시간이 없으면 실패한다.") {
|
||||||
|
every {
|
||||||
|
timeRepository.findByIdOrNull(timeId)
|
||||||
|
} returns null
|
||||||
|
|
||||||
|
shouldThrow<TimeException> {
|
||||||
|
timeFinder.findById(timeId)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe TimeErrorCode.TIME_NOT_FOUND
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context("findAllWithAvailabilityByDateAndThemeId") {
|
||||||
|
val date = LocalDate.now()
|
||||||
|
val themeId = 1L
|
||||||
|
|
||||||
|
test("테마를 찾을 수 없으면 실패한다.") {
|
||||||
|
every {
|
||||||
|
themeFinder.findById(themeId)
|
||||||
|
} throws ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
||||||
|
|
||||||
|
shouldThrow<ThemeException> {
|
||||||
|
timeFinder.findAllWithAvailabilityByDateAndThemeId(date, themeId)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe ThemeErrorCode.THEME_NOT_FOUND
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("날짜, 테마에 맞는 예약 자체가 없으면 모든 시간이 예약 가능하다.") {
|
||||||
|
every {
|
||||||
|
themeFinder.findById(themeId)
|
||||||
|
} returns mockk()
|
||||||
|
|
||||||
|
every {
|
||||||
|
reservationFinder.findAllByDateAndTheme(date, any())
|
||||||
|
} returns emptyList()
|
||||||
|
|
||||||
|
every {
|
||||||
|
timeRepository.findAll()
|
||||||
|
} returns listOf(
|
||||||
|
TimeFixture.create(startAt = LocalTime.now()),
|
||||||
|
TimeFixture.create(startAt = LocalTime.now().plusMinutes(30))
|
||||||
|
)
|
||||||
|
|
||||||
|
val result: List<TimeWithAvailability> =
|
||||||
|
timeFinder.findAllWithAvailabilityByDateAndThemeId(date, themeId)
|
||||||
|
|
||||||
|
assertSoftly(result) {
|
||||||
|
it shouldHaveSize 2
|
||||||
|
it.all { time -> time.canReserve() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
package roomescape.time.implement
|
||||||
|
|
||||||
|
import io.kotest.assertions.throwables.shouldNotThrow
|
||||||
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import roomescape.reservation.implement.ReservationFinder
|
||||||
|
import roomescape.time.exception.TimeErrorCode
|
||||||
|
import roomescape.time.exception.TimeException
|
||||||
|
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||||
|
import roomescape.time.infrastructure.persistence.TimeRepository
|
||||||
|
import roomescape.util.TimeFixture
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
|
class TimeValidatorTest : FunSpec({
|
||||||
|
val timeRepository: TimeRepository = mockk()
|
||||||
|
val reservationFinder: ReservationFinder = mockk()
|
||||||
|
|
||||||
|
val timeValidator = TimeValidator(timeRepository, reservationFinder)
|
||||||
|
|
||||||
|
context("validateIsAlreadyExists") {
|
||||||
|
val startAt = LocalTime.now()
|
||||||
|
|
||||||
|
test("같은 이메일을 가진 회원이 있으면 예외를 던진다.") {
|
||||||
|
every {
|
||||||
|
timeRepository.existsByStartAt(startAt)
|
||||||
|
} returns true
|
||||||
|
|
||||||
|
shouldThrow<TimeException> {
|
||||||
|
timeValidator.validateIsAlreadyExists(startAt)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe TimeErrorCode.TIME_DUPLICATED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("같은 이메일을 가진 회원이 없으면 종료한다.") {
|
||||||
|
every {
|
||||||
|
timeRepository.existsByStartAt(startAt)
|
||||||
|
} returns false
|
||||||
|
|
||||||
|
shouldNotThrow<TimeException> {
|
||||||
|
timeValidator.validateIsAlreadyExists(startAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context("validateIsReserved") {
|
||||||
|
val time: TimeEntity = TimeFixture.create(startAt = LocalTime.now())
|
||||||
|
|
||||||
|
test("해당 시간에 예약이 있으면 예외를 던진다.") {
|
||||||
|
every {
|
||||||
|
reservationFinder.isTimeReserved(time)
|
||||||
|
} returns true
|
||||||
|
|
||||||
|
shouldThrow<TimeException> {
|
||||||
|
timeValidator.validateIsReserved(time)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe TimeErrorCode.TIME_ALREADY_RESERVED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("해당 시간에 예약이 없으면 종료한다.") {
|
||||||
|
every {
|
||||||
|
reservationFinder.isTimeReserved(time)
|
||||||
|
} returns false
|
||||||
|
|
||||||
|
shouldNotThrow<TimeException> {
|
||||||
|
timeValidator.validateIsReserved(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
84
src/test/kotlin/roomescape/time/implement/TimeWriterTest.kt
Normal file
84
src/test/kotlin/roomescape/time/implement/TimeWriterTest.kt
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package roomescape.time.implement
|
||||||
|
|
||||||
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.mockk.*
|
||||||
|
import roomescape.time.exception.TimeErrorCode
|
||||||
|
import roomescape.time.exception.TimeException
|
||||||
|
import roomescape.time.infrastructure.persistence.TimeEntity
|
||||||
|
import roomescape.time.infrastructure.persistence.TimeRepository
|
||||||
|
import roomescape.util.TimeFixture
|
||||||
|
import roomescape.util.TsidFactory
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
|
class TimeWriterTest : FunSpec({
|
||||||
|
|
||||||
|
val timeValidator: TimeValidator = mockk()
|
||||||
|
val timeRepository: TimeRepository = mockk()
|
||||||
|
|
||||||
|
val timeWriter = TimeWriter(timeValidator, timeRepository, TsidFactory)
|
||||||
|
|
||||||
|
context("create") {
|
||||||
|
val startAt = LocalTime.now()
|
||||||
|
|
||||||
|
test("중복된 시간이 있으면 실패한다.") {
|
||||||
|
every {
|
||||||
|
timeValidator.validateIsAlreadyExists(startAt)
|
||||||
|
} throws TimeException(TimeErrorCode.TIME_DUPLICATED)
|
||||||
|
|
||||||
|
shouldThrow<TimeException> {
|
||||||
|
timeWriter.create(startAt)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe TimeErrorCode.TIME_DUPLICATED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("중복된 시간이 없으면 저장한다.") {
|
||||||
|
every {
|
||||||
|
timeValidator.validateIsAlreadyExists(startAt)
|
||||||
|
} just Runs
|
||||||
|
|
||||||
|
every {
|
||||||
|
timeRepository.save(any())
|
||||||
|
} returns TimeFixture.create(startAt = startAt)
|
||||||
|
|
||||||
|
timeWriter.create(startAt)
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
timeRepository.save(any())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context("delete") {
|
||||||
|
val time: TimeEntity = TimeFixture.create()
|
||||||
|
test("예약이 있는 시간이면 실패한다.") {
|
||||||
|
every {
|
||||||
|
timeValidator.validateIsReserved(time)
|
||||||
|
} throws TimeException(TimeErrorCode.TIME_ALREADY_RESERVED)
|
||||||
|
|
||||||
|
shouldThrow<TimeException> {
|
||||||
|
timeWriter.delete(time)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe TimeErrorCode.TIME_ALREADY_RESERVED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("예약이 없는 시간이면 제거한다.") {
|
||||||
|
every {
|
||||||
|
timeValidator.validateIsReserved(time)
|
||||||
|
} just Runs
|
||||||
|
|
||||||
|
every {
|
||||||
|
timeRepository.delete(time)
|
||||||
|
} just Runs
|
||||||
|
|
||||||
|
timeWriter.delete(time)
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
timeRepository.delete(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@ -1,28 +1,21 @@
|
|||||||
package roomescape.time.web
|
package roomescape.time.web
|
||||||
|
|
||||||
import com.ninjasquad.springmockk.MockkBean
|
import com.ninjasquad.springmockk.MockkBean
|
||||||
import com.ninjasquad.springmockk.SpykBean
|
|
||||||
import io.kotest.assertions.assertSoftly
|
import io.kotest.assertions.assertSoftly
|
||||||
import io.kotest.matchers.collections.shouldHaveSize
|
import io.kotest.matchers.collections.shouldHaveSize
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import org.hamcrest.Matchers.equalTo
|
import org.hamcrest.Matchers.equalTo
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||||
import org.springframework.context.annotation.Import
|
|
||||||
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
|
|
||||||
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.auth.exception.AuthErrorCode
|
import roomescape.auth.exception.AuthErrorCode
|
||||||
import roomescape.common.config.JacksonConfig
|
import roomescape.theme.exception.ThemeErrorCode
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
import roomescape.theme.exception.ThemeException
|
||||||
import roomescape.time.business.TimeService
|
import roomescape.time.business.TimeService
|
||||||
import roomescape.time.exception.TimeErrorCode
|
import roomescape.time.exception.TimeErrorCode
|
||||||
import roomescape.time.infrastructure.persistence.TimeEntity
|
import roomescape.time.exception.TimeException
|
||||||
import roomescape.time.infrastructure.persistence.TimeRepository
|
|
||||||
import roomescape.util.ReservationFixture
|
|
||||||
import roomescape.util.RoomescapeApiTest
|
import roomescape.util.RoomescapeApiTest
|
||||||
import roomescape.util.ThemeFixture
|
|
||||||
import roomescape.util.TimeFixture
|
import roomescape.util.TimeFixture
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
@ -32,15 +25,9 @@ class TimeControllerTest(
|
|||||||
val mockMvc: MockMvc,
|
val mockMvc: MockMvc,
|
||||||
) : RoomescapeApiTest() {
|
) : RoomescapeApiTest() {
|
||||||
|
|
||||||
@SpykBean
|
@MockkBean
|
||||||
private lateinit var timeService: TimeService
|
private lateinit var timeService: TimeService
|
||||||
|
|
||||||
@MockkBean
|
|
||||||
private lateinit var timeRepository: TimeRepository
|
|
||||||
|
|
||||||
@MockkBean
|
|
||||||
private lateinit var reservationRepository: ReservationRepository
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Given("등록된 모든 시간을 조회할 때") {
|
Given("등록된 모든 시간을 조회할 때") {
|
||||||
val endpoint = "/times"
|
val endpoint = "/times"
|
||||||
@ -52,11 +39,11 @@ class TimeControllerTest(
|
|||||||
|
|
||||||
Then("정상 응답") {
|
Then("정상 응답") {
|
||||||
every {
|
every {
|
||||||
timeRepository.findAll()
|
timeService.findTimes()
|
||||||
} returns listOf(
|
} returns listOf(
|
||||||
TimeFixture.create(id = 1L),
|
TimeFixture.create(id = 1L),
|
||||||
TimeFixture.create(id = 2L)
|
TimeFixture.create(id = 2L)
|
||||||
)
|
).toResponse()
|
||||||
|
|
||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
@ -76,7 +63,7 @@ class TimeControllerTest(
|
|||||||
loginAsUser()
|
loginAsUser()
|
||||||
val expectedError = AuthErrorCode.ACCESS_DENIED
|
val expectedError = AuthErrorCode.ACCESS_DENIED
|
||||||
|
|
||||||
Then("에러 응답을 받는다.") {
|
Then("예외 응답") {
|
||||||
runGetTest(
|
runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = endpoint,
|
endpoint = endpoint,
|
||||||
@ -85,7 +72,7 @@ class TimeControllerTest(
|
|||||||
}.andExpect {
|
}.andExpect {
|
||||||
content {
|
content {
|
||||||
contentType(MediaType.APPLICATION_JSON)
|
contentType(MediaType.APPLICATION_JSON)
|
||||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
jsonPath("$.code") { equalTo(expectedError.errorCode) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,8 +126,8 @@ class TimeControllerTest(
|
|||||||
Then("동일한 시간이 존재하면 예외 응답") {
|
Then("동일한 시간이 존재하면 예외 응답") {
|
||||||
val expectedError = TimeErrorCode.TIME_DUPLICATED
|
val expectedError = TimeErrorCode.TIME_DUPLICATED
|
||||||
every {
|
every {
|
||||||
timeRepository.existsByStartAt(time)
|
timeService.createTime(request)
|
||||||
} returns true
|
} throws TimeException(expectedError)
|
||||||
|
|
||||||
runPostTest(
|
runPostTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
@ -150,7 +137,7 @@ class TimeControllerTest(
|
|||||||
status { isEqualTo(expectedError.httpStatus.value()) }
|
status { isEqualTo(expectedError.httpStatus.value()) }
|
||||||
content {
|
content {
|
||||||
contentType(MediaType.APPLICATION_JSON)
|
contentType(MediaType.APPLICATION_JSON)
|
||||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
jsonPath("$.code") { equalTo(expectedError.errorCode) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +146,7 @@ class TimeControllerTest(
|
|||||||
When("관리자가 아닌 경우") {
|
When("관리자가 아닌 경우") {
|
||||||
loginAsUser()
|
loginAsUser()
|
||||||
|
|
||||||
Then("에러 응답을 받는다.") {
|
Then("예외 응답") {
|
||||||
val expectedError = AuthErrorCode.ACCESS_DENIED
|
val expectedError = AuthErrorCode.ACCESS_DENIED
|
||||||
runPostTest(
|
runPostTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
@ -197,9 +184,10 @@ class TimeControllerTest(
|
|||||||
Then("없는 시간을 조회하면 예외 응답") {
|
Then("없는 시간을 조회하면 예외 응답") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
val expectedError = TimeErrorCode.TIME_NOT_FOUND
|
val expectedError = TimeErrorCode.TIME_NOT_FOUND
|
||||||
|
|
||||||
every {
|
every {
|
||||||
timeRepository.findByIdOrNull(id)
|
timeService.deleteTime(id)
|
||||||
} returns null
|
} throws TimeException(expectedError)
|
||||||
|
|
||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
@ -208,7 +196,7 @@ class TimeControllerTest(
|
|||||||
status { isEqualTo(expectedError.httpStatus.value()) }
|
status { isEqualTo(expectedError.httpStatus.value()) }
|
||||||
content {
|
content {
|
||||||
contentType(MediaType.APPLICATION_JSON)
|
contentType(MediaType.APPLICATION_JSON)
|
||||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
jsonPath("$.code") { equalTo(expectedError.errorCode) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,13 +204,10 @@ class TimeControllerTest(
|
|||||||
Then("예약이 있는 시간을 삭제하면 예외 응답") {
|
Then("예약이 있는 시간을 삭제하면 예외 응답") {
|
||||||
val id = 1L
|
val id = 1L
|
||||||
val expectedError = TimeErrorCode.TIME_ALREADY_RESERVED
|
val expectedError = TimeErrorCode.TIME_ALREADY_RESERVED
|
||||||
every {
|
|
||||||
timeRepository.findByIdOrNull(id)
|
|
||||||
} returns TimeFixture.create(id = id)
|
|
||||||
|
|
||||||
every {
|
every {
|
||||||
reservationRepository.findAllByTime(any())
|
timeService.deleteTime(id)
|
||||||
} returns listOf(ReservationFixture.create())
|
} throws TimeException(expectedError)
|
||||||
|
|
||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
@ -231,7 +216,7 @@ class TimeControllerTest(
|
|||||||
status { isEqualTo(expectedError.httpStatus.value()) }
|
status { isEqualTo(expectedError.httpStatus.value()) }
|
||||||
content {
|
content {
|
||||||
contentType(MediaType.APPLICATION_JSON)
|
contentType(MediaType.APPLICATION_JSON)
|
||||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
jsonPath("$.code") { equalTo(expectedError.errorCode) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,7 +225,7 @@ class TimeControllerTest(
|
|||||||
When("관리자가 아닌 경우") {
|
When("관리자가 아닌 경우") {
|
||||||
loginAsUser()
|
loginAsUser()
|
||||||
|
|
||||||
Then("에러 응답을 받는다.") {
|
Then("예외 응답") {
|
||||||
val expectedError = AuthErrorCode.ACCESS_DENIED
|
val expectedError = AuthErrorCode.ACCESS_DENIED
|
||||||
runDeleteTest(
|
runDeleteTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
@ -254,35 +239,34 @@ class TimeControllerTest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Given("날짜, 테마가 주어졌을 때") {
|
Given("날짜, 테마가 주어졌을 때") {
|
||||||
loginAsUser()
|
beforeTest {
|
||||||
|
loginAsUser()
|
||||||
|
}
|
||||||
val date: LocalDate = LocalDate.now()
|
val date: LocalDate = LocalDate.now()
|
||||||
val themeId = 1L
|
val themeId = 1L
|
||||||
|
|
||||||
When("저장된 예약 시간이 있으면") {
|
When("테마를 찾을 수 있으면") {
|
||||||
val times: List<TimeEntity> = listOf(
|
Then("해당 날짜와 테마에 대한 예약 여부가 담긴 모든 시간을 응답") {
|
||||||
TimeFixture.create(id = 1L, startAt = LocalTime.of(10, 0)),
|
val response = TimeWithAvailabilityListResponse(
|
||||||
TimeFixture.create(id = 2L, startAt = LocalTime.of(11, 0))
|
listOf(
|
||||||
)
|
TimeWithAvailabilityResponse(id = 1L, startAt = LocalTime.now(), isAvailable = true),
|
||||||
|
TimeWithAvailabilityResponse(
|
||||||
every {
|
id = 2L,
|
||||||
timeRepository.findAll()
|
startAt = LocalTime.now().plusMinutes(30),
|
||||||
} returns times
|
isAvailable = false
|
||||||
|
),
|
||||||
Then("그 시간과, 해당 날짜와 테마에 대한 예약 여부가 담긴 목록을 응답") {
|
TimeWithAvailabilityResponse(
|
||||||
|
id = 1L,
|
||||||
every {
|
startAt = LocalTime.now().plusHours(1),
|
||||||
reservationRepository.findByDateAndThemeId(date, themeId)
|
isAvailable = true
|
||||||
} returns listOf(
|
),
|
||||||
ReservationFixture.create(
|
|
||||||
id = 1L,
|
|
||||||
date = date,
|
|
||||||
theme = ThemeFixture.create(id = themeId),
|
|
||||||
time = times[0]
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
every {
|
||||||
|
timeService.findTimesWithAvailability(date, themeId)
|
||||||
|
} returns response
|
||||||
|
|
||||||
val response = runGetTest(
|
val result = runGetTest(
|
||||||
mockMvc = mockMvc,
|
mockMvc = mockMvc,
|
||||||
endpoint = "/times/search?date=$date&themeId=$themeId",
|
endpoint = "/times/search?date=$date&themeId=$themeId",
|
||||||
) {
|
) {
|
||||||
@ -292,13 +276,30 @@ class TimeControllerTest(
|
|||||||
}
|
}
|
||||||
}.andReturn().readValue(TimeWithAvailabilityListResponse::class.java)
|
}.andReturn().readValue(TimeWithAvailabilityListResponse::class.java)
|
||||||
|
|
||||||
assertSoftly(response.times) {
|
assertSoftly(result.times) {
|
||||||
this shouldHaveSize times.size
|
this shouldHaveSize response.times.size
|
||||||
this[0].id shouldBe times[0].id
|
this[0].id shouldBe response.times[0].id
|
||||||
this[0].isAvailable shouldBe false
|
this[0].isAvailable shouldBe response.times[0].isAvailable
|
||||||
|
|
||||||
this[1].id shouldBe times[1].id
|
this[1].id shouldBe response.times[1].id
|
||||||
this[1].isAvailable shouldBe true
|
this[1].isAvailable shouldBe response.times[1].isAvailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
When("테마를 찾을 수 없으면") {
|
||||||
|
val expectedError = ThemeErrorCode.THEME_NOT_FOUND
|
||||||
|
every {
|
||||||
|
timeService.findTimesWithAvailability(date, themeId)
|
||||||
|
} throws ThemeException(expectedError)
|
||||||
|
|
||||||
|
Then("예외 응답") {
|
||||||
|
runGetTest(
|
||||||
|
mockMvc = mockMvc,
|
||||||
|
endpoint = "/times/search?date=$date&themeId=$themeId",
|
||||||
|
) {
|
||||||
|
status { isNotFound() }
|
||||||
|
jsonPath("$.code", equalTo(expectedError.errorCode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user