generated from pricelees/issue-pr-template
[#30] 코드 구조 개선 #31
@ -1,87 +1,108 @@
|
||||
package roomescape.theme.business
|
||||
|
||||
import io.kotest.assertions.assertSoftly
|
||||
import io.kotest.assertions.throwables.shouldNotThrow
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldContainExactly
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import io.mockk.*
|
||||
import roomescape.theme.exception.ThemeErrorCode
|
||||
import roomescape.theme.exception.ThemeException
|
||||
import roomescape.theme.implement.ThemeFinder
|
||||
import roomescape.theme.implement.ThemeWriter
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||
import roomescape.theme.web.ThemeCreateRequest
|
||||
import roomescape.theme.web.ThemeRetrieveResponse
|
||||
import roomescape.util.TsidFactory
|
||||
import roomescape.theme.web.ThemeCreateResponse
|
||||
import roomescape.util.ThemeFixture
|
||||
import java.time.LocalDate
|
||||
|
||||
class ThemeServiceTest : FunSpec({
|
||||
val themeFinder: ThemeFinder = mockk()
|
||||
val themeWriter: ThemeWriter = mockk()
|
||||
|
||||
val themeRepository: ThemeRepository = mockk()
|
||||
val themeService = ThemeService(TsidFactory, themeRepository)
|
||||
val themeService = ThemeService(themeFinder, themeWriter)
|
||||
|
||||
context("findThemeById") {
|
||||
val themeId = 1L
|
||||
test("조회 성공") {
|
||||
|
||||
test("정상 응답") {
|
||||
val theme: ThemeEntity = ThemeFixture.create(id = themeId)
|
||||
every {
|
||||
themeRepository.findByIdOrNull(themeId)
|
||||
themeFinder.findById(themeId)
|
||||
} returns theme
|
||||
|
||||
theme.id shouldBe themeId
|
||||
}
|
||||
|
||||
test("ID로 테마를 찾을 수 없으면 400 예외를 던진다.") {
|
||||
test("테마를 찾을 수 없으면 예외 응답") {
|
||||
every {
|
||||
themeRepository.findByIdOrNull(themeId)
|
||||
} returns null
|
||||
themeFinder.findById(themeId)
|
||||
} throws ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
||||
|
||||
val exception = shouldThrow<ThemeException> {
|
||||
shouldThrow<ThemeException> {
|
||||
themeService.findById(themeId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_NOT_FOUND
|
||||
}
|
||||
|
||||
exception.errorCode shouldBe ThemeErrorCode.THEME_NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
context("findAllThemes") {
|
||||
test("모든 테마를 조회한다.") {
|
||||
val themes = listOf(ThemeFixture.create(id = 1, name = "t1"), ThemeFixture.create(id = 2, name = "t2"))
|
||||
context("findThemes") {
|
||||
test("정상 응답") {
|
||||
val themes = listOf(
|
||||
ThemeFixture.create(id = 1, name = "t1"),
|
||||
ThemeFixture.create(id = 2, name = "t2")
|
||||
)
|
||||
|
||||
every {
|
||||
themeRepository.findAll()
|
||||
themeFinder.findAll()
|
||||
} returns themes
|
||||
|
||||
assertSoftly(themeService.findThemes()) {
|
||||
this.themes.size shouldBe themes.size
|
||||
this.themes[0].name shouldBe "t1"
|
||||
this.themes[1].name shouldBe "t2"
|
||||
val response = themeService.findThemes()
|
||||
|
||||
assertSoftly(response.themes) {
|
||||
this.size shouldBe themes.size
|
||||
it.map { theme -> theme.name } shouldContainExactly themes.map { theme -> theme.name }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("save") {
|
||||
context("findMostReservedThemes") {
|
||||
test("7일 전 부터 1일 전 까지 조회") {
|
||||
val count = 10
|
||||
val startFrom = slot<LocalDate>()
|
||||
val endAt = slot<LocalDate>()
|
||||
|
||||
every {
|
||||
themeFinder.findMostReservedThemes(count, capture(startFrom), capture(endAt))
|
||||
} returns emptyList()
|
||||
|
||||
themeService.findMostReservedThemes(count)
|
||||
|
||||
startFrom.captured shouldBe LocalDate.now().minusDays(7)
|
||||
endAt.captured shouldBe LocalDate.now().minusDays(1)
|
||||
}
|
||||
}
|
||||
|
||||
context("createTheme") {
|
||||
val request = ThemeCreateRequest(
|
||||
name = "New Theme",
|
||||
description = "Description",
|
||||
thumbnail = "http://example.com/thumbnail.jpg"
|
||||
)
|
||||
|
||||
test("저장 성공") {
|
||||
test("정상 저장") {
|
||||
every {
|
||||
themeRepository.existsByName(request.name)
|
||||
} returns false
|
||||
|
||||
every {
|
||||
themeRepository.save(any())
|
||||
themeWriter.create(request.name, request.description, request.thumbnail)
|
||||
} returns ThemeFixture.create(
|
||||
id = 1L,
|
||||
id = 1,
|
||||
name = request.name,
|
||||
description = request.description,
|
||||
thumbnail = request.thumbnail
|
||||
)
|
||||
|
||||
val response: ThemeRetrieveResponse = themeService.createTheme(request)
|
||||
val response: ThemeCreateResponse = themeService.createTheme(request)
|
||||
|
||||
assertSoftly(response) {
|
||||
this.id shouldBe 1L
|
||||
@ -91,32 +112,51 @@ class ThemeServiceTest : FunSpec({
|
||||
}
|
||||
}
|
||||
|
||||
test("테마 이름이 중복되면 409 예외를 던진다.") {
|
||||
test("중복된 이름이 있으면 예외 응답") {
|
||||
every {
|
||||
themeRepository.existsByName(request.name)
|
||||
} returns true
|
||||
themeWriter.create(request.name, request.description, request.thumbnail)
|
||||
} throws ThemeException(ThemeErrorCode.THEME_NAME_DUPLICATED)
|
||||
|
||||
val exception = shouldThrow<ThemeException> {
|
||||
shouldThrow<ThemeException> {
|
||||
themeService.createTheme(request)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_NAME_DUPLICATED
|
||||
}
|
||||
|
||||
exception.errorCode shouldBe ThemeErrorCode.THEME_NAME_DUPLICATED
|
||||
}
|
||||
}
|
||||
|
||||
context("deleteById") {
|
||||
test("이미 예약 중인 테마라면 409 예외를 던진다.") {
|
||||
val themeId = 1L
|
||||
val themeId = 1L
|
||||
val theme: ThemeEntity = ThemeFixture.create(id = themeId)
|
||||
|
||||
every {
|
||||
themeRepository.isReservedTheme(themeId)
|
||||
} returns true
|
||||
test("정상 응답") {
|
||||
every { themeFinder.findById(themeId) } returns theme
|
||||
every { themeWriter.delete(theme) } just Runs
|
||||
|
||||
val exception = shouldThrow<ThemeException> {
|
||||
shouldNotThrow<ThemeException> {
|
||||
themeService.deleteTheme(themeId)
|
||||
}
|
||||
}
|
||||
|
||||
exception.errorCode shouldBe ThemeErrorCode.THEME_ALREADY_RESERVED
|
||||
test("테마를 찾을 수 없으면 예외 응답") {
|
||||
every { themeFinder.findById(themeId) } throws ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
||||
|
||||
shouldThrow<ThemeException> {
|
||||
themeService.deleteTheme(themeId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
test("예약이 있는 테마이면 예외 응답") {
|
||||
every { themeFinder.findById(themeId) } returns theme
|
||||
every { themeWriter.delete(theme) } throws ThemeException(ThemeErrorCode.THEME_ALREADY_RESERVED)
|
||||
|
||||
shouldThrow<ThemeException> {
|
||||
themeService.deleteTheme(themeId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_ALREADY_RESERVED
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
package roomescape.theme.implement
|
||||
|
||||
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.theme.exception.ThemeErrorCode
|
||||
import roomescape.theme.exception.ThemeException
|
||||
import roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||
import roomescape.util.ThemeFixture
|
||||
import java.time.LocalDate
|
||||
|
||||
class ThemeFinderTest : FunSpec({
|
||||
val themeRepository: ThemeRepository = mockk()
|
||||
|
||||
val themeFinder = ThemeFinder(themeRepository)
|
||||
|
||||
context("findAll") {
|
||||
test("모든 테마를 조회한다.") {
|
||||
every {
|
||||
themeRepository.findAll()
|
||||
} returns listOf(mockk(), mockk(), mockk())
|
||||
|
||||
themeRepository.findAll() shouldHaveSize 3
|
||||
}
|
||||
}
|
||||
|
||||
context("findById") {
|
||||
val timeId = 1L
|
||||
test("동일한 ID인 테마를 찾아 응답한다.") {
|
||||
every {
|
||||
themeRepository.findByIdOrNull(timeId)
|
||||
} returns mockk()
|
||||
|
||||
themeFinder.findById(timeId)
|
||||
|
||||
verify(exactly = 1) {
|
||||
themeRepository.findByIdOrNull(timeId)
|
||||
}
|
||||
}
|
||||
|
||||
test("동일한 ID인 테마가 없으면 실패한다.") {
|
||||
every {
|
||||
themeRepository.findByIdOrNull(timeId)
|
||||
} returns null
|
||||
|
||||
shouldThrow<ThemeException> {
|
||||
themeFinder.findById(timeId)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("findMostReservedThemes") {
|
||||
test("입력된 개수보다 조회된 개수가 작으면 조회된 개수만큼 반환한다.") {
|
||||
val count = 10
|
||||
val startFrom = LocalDate.now().minusDays(7)
|
||||
val endAt = LocalDate.now().minusDays(1)
|
||||
|
||||
every {
|
||||
themeRepository.findPopularThemes(startFrom, endAt, count)
|
||||
} returns listOf(
|
||||
ThemeFixture.create(id = 1, name = "name1"),
|
||||
ThemeFixture.create(id = 2, name = "name2"),
|
||||
ThemeFixture.create(id = 3, name = "name3"),
|
||||
)
|
||||
|
||||
themeFinder.findMostReservedThemes(count, startFrom, endAt) shouldHaveSize 3
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,79 @@
|
||||
package roomescape.theme.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.theme.exception.ThemeErrorCode
|
||||
import roomescape.theme.exception.ThemeException
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||
import roomescape.util.ThemeFixture
|
||||
|
||||
class ThemeValidatorTest : FunSpec({
|
||||
val themeRepository: ThemeRepository = mockk()
|
||||
|
||||
val themeValidator = ThemeValidator(themeRepository)
|
||||
|
||||
context("validateNameAlreadyExists") {
|
||||
val name = "name"
|
||||
|
||||
test("같은 이름을 가진 테마가 있으면 예외를 던진다.") {
|
||||
every {
|
||||
themeRepository.existsByName(name)
|
||||
} returns true
|
||||
|
||||
shouldThrow<ThemeException> {
|
||||
themeValidator.validateNameAlreadyExists(name)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_NAME_DUPLICATED
|
||||
}
|
||||
}
|
||||
|
||||
test("같은 이름을 가진 테마가 없으면 종료한다.") {
|
||||
every {
|
||||
themeRepository.existsByName(name)
|
||||
} returns false
|
||||
|
||||
shouldNotThrow<ThemeException> {
|
||||
themeValidator.validateNameAlreadyExists(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("validateIsReserved") {
|
||||
test("입력된 id가 null 이면 예외를 던진다.") {
|
||||
shouldThrow<ThemeException> {
|
||||
themeValidator.validateIsReserved(ThemeFixture.create(id = null))
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.INVALID_REQUEST_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
val theme: ThemeEntity = ThemeFixture.create(id = 1L, name = "name")
|
||||
|
||||
test("예약이 있는 테마이면 예외를 던진다.") {
|
||||
every {
|
||||
themeRepository.isReservedTheme(theme.id!!)
|
||||
} returns true
|
||||
|
||||
shouldThrow<ThemeException> {
|
||||
themeValidator.validateIsReserved(theme)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_ALREADY_RESERVED
|
||||
}
|
||||
}
|
||||
|
||||
test("예약이 없는 테마이면 종료한다.") {
|
||||
every {
|
||||
themeRepository.isReservedTheme(theme.id!!)
|
||||
} returns false
|
||||
|
||||
shouldNotThrow<ThemeException> {
|
||||
themeValidator.validateIsReserved(theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -0,0 +1,86 @@
|
||||
package roomescape.theme.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.*
|
||||
import roomescape.common.config.next
|
||||
import roomescape.theme.exception.ThemeErrorCode
|
||||
import roomescape.theme.exception.ThemeException
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||
import roomescape.util.ThemeFixture
|
||||
import roomescape.util.TsidFactory
|
||||
|
||||
class ThemeWriterTest : FunSpec({
|
||||
|
||||
val themeValidator: ThemeValidator = mockk()
|
||||
val themeRepository: ThemeRepository = mockk()
|
||||
|
||||
val themeWriter = ThemeWriter(themeValidator, themeRepository, TsidFactory)
|
||||
|
||||
context("create") {
|
||||
val name = "name"
|
||||
val description = "description"
|
||||
val thumbnail = "thumbnail"
|
||||
|
||||
test("중복된 이름이 있으면 실패한다.") {
|
||||
every {
|
||||
themeValidator.validateNameAlreadyExists(name)
|
||||
} throws ThemeException(ThemeErrorCode.THEME_NAME_DUPLICATED)
|
||||
|
||||
shouldThrow<ThemeException> {
|
||||
themeWriter.create(name, description, thumbnail)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_NAME_DUPLICATED
|
||||
}
|
||||
}
|
||||
|
||||
test("중복된 이름이 없으면 저장한다.") {
|
||||
every {
|
||||
themeValidator.validateNameAlreadyExists(name)
|
||||
} just Runs
|
||||
|
||||
every {
|
||||
themeRepository.save(any())
|
||||
} returns ThemeFixture.create(name = name, description = description, thumbnail = thumbnail)
|
||||
|
||||
themeWriter.create(name, description, thumbnail)
|
||||
|
||||
verify(exactly = 1) {
|
||||
themeRepository.save(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("delete") {
|
||||
val theme: ThemeEntity = ThemeFixture.create(id = TsidFactory.next())
|
||||
test("예약이 있는 테마이면 실패한다.") {
|
||||
every {
|
||||
themeValidator.validateIsReserved(theme)
|
||||
} throws ThemeException(ThemeErrorCode.THEME_ALREADY_RESERVED)
|
||||
|
||||
shouldThrow<ThemeException> {
|
||||
themeWriter.delete(theme)
|
||||
}.also {
|
||||
it.errorCode shouldBe ThemeErrorCode.THEME_ALREADY_RESERVED
|
||||
}
|
||||
}
|
||||
|
||||
test("예약이 없는 테마이면 제거한다.") {
|
||||
every {
|
||||
themeValidator.validateIsReserved(theme)
|
||||
} just Runs
|
||||
|
||||
every {
|
||||
themeRepository.delete(theme)
|
||||
} just Runs
|
||||
|
||||
themeWriter.delete(theme)
|
||||
|
||||
verify(exactly = 1) {
|
||||
themeRepository.delete(theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -5,7 +5,7 @@ import io.kotest.matchers.collections.shouldContainInOrder
|
||||
import io.kotest.matchers.shouldBe
|
||||
import jakarta.persistence.EntityManager
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import roomescape.theme.util.TestThemeCreateUtil
|
||||
import roomescape.theme.util.TestThemeDataHelper
|
||||
import java.time.LocalDate
|
||||
|
||||
@DataJpaTest(showSql = false)
|
||||
@ -14,12 +14,13 @@ class ThemeRepositoryTest(
|
||||
val entityManager: EntityManager
|
||||
) : FunSpec() {
|
||||
|
||||
val helper = TestThemeDataHelper(entityManager, transactionTemplate = null)
|
||||
|
||||
init {
|
||||
context("findTopNThemeBetweenStartDateAndEndDate") {
|
||||
beforeTest {
|
||||
for (i in 1..10) {
|
||||
TestThemeCreateUtil.createThemeWithReservations(
|
||||
entityManager = entityManager,
|
||||
helper.createThemeWithReservations(
|
||||
name = "테마$i",
|
||||
reservedCount = i,
|
||||
date = LocalDate.now().minusDays(i.toLong()),
|
||||
@ -27,7 +28,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
}
|
||||
|
||||
test("지난 10일간 예약 수가 가장 많은 테마 5개를 조회한다.") {
|
||||
test("지난 10일간 예약 수가 가장 많은 테마 5개를 조회") {
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().minusDays(10),
|
||||
LocalDate.now().minusDays(1),
|
||||
@ -40,7 +41,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
}
|
||||
|
||||
test("8일 전부터 5일 전까지 예약 수가 가장 많은 테마 3개를 조회한다.") {
|
||||
test("8일 전부터 5일 전까지 예약 수가 가장 많은 테마 3개를 조회") {
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().minusDays(8),
|
||||
LocalDate.now().minusDays(5),
|
||||
@ -53,9 +54,8 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
}
|
||||
|
||||
test("예약 수가 동일하면 먼저 생성된 테마를 우선 조회한다.") {
|
||||
TestThemeCreateUtil.createThemeWithReservations(
|
||||
entityManager = entityManager,
|
||||
test("예약 수가 동일하면 먼저 생성된 테마를 우선 조회") {
|
||||
helper.createThemeWithReservations(
|
||||
name = "테마11",
|
||||
reservedCount = 5,
|
||||
date = LocalDate.now().minusDays(5),
|
||||
@ -73,7 +73,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
}
|
||||
|
||||
test("입력된 갯수보다 조회된 갯수가 작으면, 조회된 갯수만큼 반환한다.") {
|
||||
test("입력된 갯수보다 조회된 갯수가 작으면, 조회된 갯수만큼 반환") {
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().minusDays(10),
|
||||
LocalDate.now().minusDays(6),
|
||||
@ -83,7 +83,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
}
|
||||
|
||||
test("입력된 갯수보다 조회된 갯수가 많으면, 입력된 갯수만큼 반환한다.") {
|
||||
test("입력된 갯수보다 조회된 갯수가 많으면, 입력된 갯수만큼 반환") {
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().minusDays(10),
|
||||
LocalDate.now().minusDays(1),
|
||||
@ -93,7 +93,7 @@ class ThemeRepositoryTest(
|
||||
}
|
||||
}
|
||||
|
||||
test("입력된 날짜 범위에 예약된 테마가 없을 경우 빈 리스트를 반환한다.") {
|
||||
test("입력된 날짜 범위에 예약된 테마가 없을 경우 빈 리스트 반환") {
|
||||
themeRepository.findPopularThemes(
|
||||
LocalDate.now().plusDays(1),
|
||||
LocalDate.now().plusDays(10),
|
||||
@ -106,26 +106,24 @@ class ThemeRepositoryTest(
|
||||
context("existsByName ") {
|
||||
val themeName = "test-theme"
|
||||
beforeTest {
|
||||
TestThemeCreateUtil.createThemeWithReservations(
|
||||
entityManager = entityManager,
|
||||
helper.createThemeWithReservations(
|
||||
name = themeName,
|
||||
reservedCount = 0,
|
||||
date = LocalDate.now()
|
||||
)
|
||||
}
|
||||
test("테마 이름이 존재하면 true를 반환한다.") {
|
||||
test("테마 이름이 존재하면 true 반환") {
|
||||
themeRepository.existsByName(themeName) shouldBe true
|
||||
}
|
||||
|
||||
test("테마 이름이 존재하지 않으면 false를 반환한다.") {
|
||||
test("테마 이름이 존재하지 않으면 false 반환") {
|
||||
themeRepository.existsByName(themeName.repeat(2)) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
context("isReservedTheme") {
|
||||
test("테마가 예약 중이면 true를 반환한다.") {
|
||||
val theme = TestThemeCreateUtil.createThemeWithReservations(
|
||||
entityManager = entityManager,
|
||||
test("테마가 예약 중이면 true 반환") {
|
||||
val theme = helper.createThemeWithReservations(
|
||||
name = "예약된 테마",
|
||||
reservedCount = 1,
|
||||
date = LocalDate.now()
|
||||
@ -133,9 +131,8 @@ class ThemeRepositoryTest(
|
||||
themeRepository.isReservedTheme(theme.id!!) shouldBe true
|
||||
}
|
||||
|
||||
test("테마가 예약 중이 아니면 false를 반환한다.") {
|
||||
val theme = TestThemeCreateUtil.createThemeWithReservations(
|
||||
entityManager = entityManager,
|
||||
test("테마가 예약 중이 아니면 false 반환") {
|
||||
val theme = helper.createThemeWithReservations(
|
||||
name = "예약되지 않은 테마",
|
||||
reservedCount = 0,
|
||||
date = LocalDate.now()
|
||||
|
||||
@ -9,7 +9,7 @@ import org.hamcrest.Matchers.equalTo
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.web.server.LocalServerPort
|
||||
import org.springframework.transaction.support.TransactionTemplate
|
||||
import roomescape.theme.util.TestThemeCreateUtil
|
||||
import roomescape.theme.util.TestThemeDataHelper
|
||||
import roomescape.util.CleanerMode
|
||||
import roomescape.util.DatabaseCleanerExtension
|
||||
import java.time.LocalDate
|
||||
@ -20,16 +20,16 @@ class MostReservedThemeApiTest(
|
||||
@LocalServerPort val port: Int,
|
||||
val transactionTemplate: TransactionTemplate,
|
||||
val entityManager: EntityManager,
|
||||
) : FunSpec({
|
||||
extension(DatabaseCleanerExtension(mode = CleanerMode.AFTER_SPEC))
|
||||
}) {
|
||||
) : FunSpec({ extension(DatabaseCleanerExtension(mode = CleanerMode.AFTER_SPEC)) }) {
|
||||
|
||||
val helper = TestThemeDataHelper(entityManager, transactionTemplate)
|
||||
|
||||
init {
|
||||
beforeSpec {
|
||||
transactionTemplate.executeWithoutResult {
|
||||
// 지난 7일간 예약된 테마 10개 생성
|
||||
// 7일 전 ~ 1일 전 예약된 테마 10개 생성
|
||||
for (i in 1..10) {
|
||||
TestThemeCreateUtil.createThemeWithReservations(
|
||||
entityManager = entityManager,
|
||||
helper.createThemeWithReservations(
|
||||
name = "테마$i",
|
||||
reservedCount = 1,
|
||||
date = LocalDate.now().minusDays(Random.nextLong(1, 7))
|
||||
@ -37,16 +37,22 @@ class MostReservedThemeApiTest(
|
||||
}
|
||||
|
||||
// 8일 전 예약된 테마 1개 생성
|
||||
TestThemeCreateUtil.createThemeWithReservations(
|
||||
entityManager = entityManager,
|
||||
helper.createThemeWithReservations(
|
||||
name = "테마11",
|
||||
reservedCount = 1,
|
||||
date = LocalDate.now().minusDays(8)
|
||||
)
|
||||
|
||||
// 당일 예약된 테마 1개 생성
|
||||
helper.createThemeWithReservations(
|
||||
name = "테마12",
|
||||
reservedCount = 1,
|
||||
date = LocalDate.now()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("지난 주 가장 많이 예약된 테마 API") {
|
||||
context("GET /themes/most-reserved-last-week") {
|
||||
val endpoint = "/themes/most-reserved-last-week"
|
||||
|
||||
test("count 파라미터가 없으면 10개를 반환한다") {
|
||||
@ -87,8 +93,8 @@ class MostReservedThemeApiTest(
|
||||
}
|
||||
|
||||
test("지난 7일 동안의 예약만 집계한다") {
|
||||
// 8일 전에 예약된 테마는 집계에서 제외되어야 한다.
|
||||
val count = 11
|
||||
// beforeSpec 에서 정의한 테스트 데이터 중, 8일 전 / 당일 예약 테마는 제외되어야 한다.
|
||||
val count = 12
|
||||
Given {
|
||||
port(port)
|
||||
param("count", count)
|
||||
|
||||
@ -1,42 +1,32 @@
|
||||
package roomescape.theme.web
|
||||
|
||||
import com.ninjasquad.springmockk.MockkBean
|
||||
import com.ninjasquad.springmockk.SpykBean
|
||||
import io.kotest.assertions.assertSoftly
|
||||
import io.kotest.matchers.collections.shouldContainAll
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.runs
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import roomescape.auth.exception.AuthErrorCode
|
||||
import roomescape.common.exception.CommonErrorCode
|
||||
import roomescape.theme.business.ThemeService
|
||||
import roomescape.theme.exception.ThemeErrorCode
|
||||
import roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||
import roomescape.theme.exception.ThemeException
|
||||
import roomescape.util.RoomescapeApiTest
|
||||
import roomescape.util.ThemeFixture
|
||||
|
||||
@WebMvcTest(ThemeController::class)
|
||||
class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
|
||||
@SpykBean
|
||||
class ThemeControllerTest(val mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
@MockkBean
|
||||
private lateinit var themeService: ThemeService
|
||||
|
||||
@MockkBean
|
||||
private lateinit var themeRepository: ThemeRepository
|
||||
|
||||
init {
|
||||
Given("모든 테마를 조회할 때") {
|
||||
Given("GET /themes 요청을") {
|
||||
val endpoint = "/themes"
|
||||
|
||||
When("로그인 상태가 아니라면") {
|
||||
When("로그인 하지 않은 사용자가 보내면") {
|
||||
doNotLogin()
|
||||
|
||||
Then("에러 응답을 받는다.") {
|
||||
Then("예외 응답") {
|
||||
val expectedError = AuthErrorCode.MEMBER_NOT_FOUND
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
@ -52,34 +42,32 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
When("로그인 상태라면") {
|
||||
loginAsUser()
|
||||
|
||||
Then("조회에 성공한다.") {
|
||||
Then("정상 응답") {
|
||||
every {
|
||||
themeRepository.findAll()
|
||||
themeService.findThemes()
|
||||
} returns listOf(
|
||||
ThemeFixture.create(id = 1, name = "theme1"),
|
||||
ThemeFixture.create(id = 2, name = "theme2"),
|
||||
ThemeFixture.create(id = 3, name = "theme3")
|
||||
)
|
||||
).toRetrieveListResponse()
|
||||
|
||||
val response: ThemeRetrieveListResponse = runGetTest(
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
) {
|
||||
status { isOk() }
|
||||
content {
|
||||
contentType(MediaType.APPLICATION_JSON)
|
||||
jsonPath("$.data.themes[0].id") { value(1) }
|
||||
jsonPath("$.data.themes[1].id") { value(2) }
|
||||
jsonPath("$.data.themes[2].id") { value(3) }
|
||||
}
|
||||
}.andReturn().readValue(ThemeRetrieveListResponse::class.java)
|
||||
|
||||
assertSoftly(response.themes) {
|
||||
it.size shouldBe 3
|
||||
it.map { m -> m.name } shouldContainAll listOf("theme1", "theme2", "theme3")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Given("테마를 추가할 때") {
|
||||
Given("POST /themes 요청을") {
|
||||
val endpoint = "/themes"
|
||||
val request = ThemeCreateRequest(
|
||||
name = "theme1",
|
||||
@ -87,9 +75,10 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
thumbnail = "http://example.com/thumbnail1.jpg"
|
||||
)
|
||||
|
||||
When("로그인 상태가 아니라면") {
|
||||
When("로그인 하지 않은 사용자가 보내면") {
|
||||
doNotLogin()
|
||||
Then("에러 응답을 받는다.") {
|
||||
|
||||
Then("예외 응답") {
|
||||
val expectedError = AuthErrorCode.MEMBER_NOT_FOUND
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
@ -102,9 +91,10 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
}
|
||||
}
|
||||
|
||||
When("관리자가 아닌 회원은") {
|
||||
When("관리자가 아닌 사용자가 보내면") {
|
||||
loginAsUser()
|
||||
Then("에러 응답을 받는다.") {
|
||||
|
||||
Then("예외 응답") {
|
||||
val expectedError = AuthErrorCode.ACCESS_DENIED
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
@ -117,15 +107,17 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
}
|
||||
}
|
||||
|
||||
When("동일한 이름의 테마가 있으면") {
|
||||
loginAsAdmin()
|
||||
When("관리자가 보낼 때") {
|
||||
beforeTest {
|
||||
loginAsAdmin()
|
||||
}
|
||||
|
||||
val expectedError = ThemeErrorCode.THEME_NAME_DUPLICATED
|
||||
Then("동일한 이름의 테마가 있으면 예외 응답") {
|
||||
val expectedError = ThemeErrorCode.THEME_NAME_DUPLICATED
|
||||
|
||||
Then("에러 응답을 받는다.") {
|
||||
every {
|
||||
themeRepository.existsByName(request.name)
|
||||
} returns true
|
||||
themeService.createTheme(request)
|
||||
} throws ThemeException(expectedError)
|
||||
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
@ -136,80 +128,70 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When("값이 잘못 입력되면 400 에러를 응답한다") {
|
||||
beforeTest {
|
||||
loginAsAdmin()
|
||||
}
|
||||
When("입력 값의 형식이 잘못되면 예외 응답") {
|
||||
val request = ThemeCreateRequest(
|
||||
name = "theme1",
|
||||
description = "description1",
|
||||
thumbnail = "http://example.com/thumbnail1.jpg"
|
||||
)
|
||||
|
||||
val request = ThemeCreateRequest(
|
||||
name = "theme1",
|
||||
description = "description1",
|
||||
thumbnail = "http://example.com/thumbnail1.jpg"
|
||||
)
|
||||
fun runTest(request: ThemeCreateRequest) {
|
||||
val expectedError = CommonErrorCode.INVALID_INPUT_VALUE
|
||||
|
||||
fun runTest(request: ThemeCreateRequest) {
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request,
|
||||
) {
|
||||
status { isBadRequest() }
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request,
|
||||
) {
|
||||
status { isEqualTo(expectedError.httpStatus.value()) }
|
||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
||||
}
|
||||
}
|
||||
|
||||
Then("이름이 공백인 경우") {
|
||||
val invalidRequest = request.copy(name = " ")
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
|
||||
Then("이름이 20글자를 초과하는 경우") {
|
||||
val invalidRequest = request.copy(name = "a".repeat(21))
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
|
||||
Then("설명이 공백인 경우") {
|
||||
val invalidRequest = request.copy(description = " ")
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
|
||||
Then("설명이 100글자를 초과하는 경우") {
|
||||
val invalidRequest = request.copy(description = "a".repeat(101))
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
|
||||
Then("썸네일이 공백인 경우") {
|
||||
val invalidRequest = request.copy(thumbnail = " ")
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
|
||||
Then("썸네일이 URL 형식이 아닌 경우") {
|
||||
val invalidRequest = request.copy(thumbnail = "invalid-url")
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
}
|
||||
|
||||
Then("이름이 공백인 경우") {
|
||||
val invalidRequest = request.copy(name = " ")
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
Then("정상 응답") {
|
||||
val theme = ThemeFixture.create(
|
||||
id = 1,
|
||||
name = request.name,
|
||||
description = request.description,
|
||||
thumbnail = request.thumbnail
|
||||
)
|
||||
|
||||
Then("이름이 20글자를 초과하는 경우") {
|
||||
val invalidRequest = request.copy(name = "a".repeat(21))
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
every {
|
||||
themeService.createTheme(request)
|
||||
} returns theme.toCreateResponse()
|
||||
|
||||
Then("설명이 공백인 경우") {
|
||||
val invalidRequest = request.copy(description = " ")
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
|
||||
Then("설명이 100글자를 초과하는 경우") {
|
||||
val invalidRequest = request.copy(description = "a".repeat(101))
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
|
||||
Then("썸네일이 공백인 경우") {
|
||||
val invalidRequest = request.copy(thumbnail = " ")
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
|
||||
Then("썸네일이 URL 형식이 아닌 경우") {
|
||||
val invalidRequest = request.copy(thumbnail = "invalid-url")
|
||||
runTest(invalidRequest)
|
||||
}
|
||||
}
|
||||
|
||||
When("저장에 성공하면") {
|
||||
loginAsAdmin()
|
||||
|
||||
val theme = ThemeFixture.create(
|
||||
id = 1,
|
||||
name = request.name,
|
||||
description = request.description,
|
||||
thumbnail = request.thumbnail
|
||||
)
|
||||
|
||||
every {
|
||||
themeService.createTheme(request)
|
||||
} returns ThemeRetrieveResponse(
|
||||
id = theme.id!!,
|
||||
name = theme.name,
|
||||
description = theme.description,
|
||||
thumbnail = theme.thumbnail
|
||||
)
|
||||
|
||||
Then("201 응답을 받는다.") {
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
@ -228,13 +210,14 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
}
|
||||
}
|
||||
|
||||
Given("테마를 제거할 때") {
|
||||
Given("DELETE /themes/{id} 요청을") {
|
||||
val themeId = 1L
|
||||
val endpoint = "/themes/$themeId"
|
||||
|
||||
When("로그인 상태가 아니라면") {
|
||||
doNotLogin()
|
||||
Then("에러 응답을 받는다.") {
|
||||
When("관리자가 아닌 사용자가 보내면 예외 응답") {
|
||||
Then("로그인 하지 않은 경우") {
|
||||
doNotLogin()
|
||||
|
||||
val expectedError = AuthErrorCode.MEMBER_NOT_FOUND
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
@ -244,11 +227,10 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
jsonPath("$.code", equalTo(expectedError.errorCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When("관리자가 아닌 회원은") {
|
||||
loginAsUser()
|
||||
Then("에러 응답을 받는다.") {
|
||||
Then("로그인은 하였으나 관리자가 아닌 경우") {
|
||||
loginAsUser()
|
||||
|
||||
val expectedError = AuthErrorCode.ACCESS_DENIED
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
@ -260,14 +242,16 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
}
|
||||
}
|
||||
|
||||
When("이미 예약된 테마이면") {
|
||||
loginAsAdmin()
|
||||
val expectedError = ThemeErrorCode.THEME_ALREADY_RESERVED
|
||||
When("관리자가 보낼 때") {
|
||||
beforeTest {
|
||||
loginAsAdmin()
|
||||
}
|
||||
|
||||
Then("에러 응답을 받는다.") {
|
||||
Then("이미 예약된 테마이면 예외 응답") {
|
||||
val expectedError = ThemeErrorCode.THEME_ALREADY_RESERVED
|
||||
every {
|
||||
themeRepository.isReservedTheme(themeId)
|
||||
} returns true
|
||||
themeService.deleteTheme(themeId)
|
||||
} throws ThemeException(expectedError)
|
||||
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
@ -277,20 +261,10 @@ class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When("정상적으로 제거되면") {
|
||||
loginAsAdmin()
|
||||
Then("정상 응답") {
|
||||
every { themeService.deleteTheme(themeId) } returns Unit
|
||||
|
||||
every {
|
||||
themeRepository.isReservedTheme(themeId)
|
||||
} returns false
|
||||
|
||||
every {
|
||||
themeRepository.deleteById(themeId)
|
||||
} just runs
|
||||
|
||||
Then("204 응답을 받는다.") {
|
||||
runDeleteTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user