generated from pricelees/issue-pr-template
Compare commits
4 Commits
main
...
refactor/#
| Author | SHA1 | Date | |
|---|---|---|---|
| af551e80bb | |||
| 92b76f95f8 | |||
| a15adcbd16 | |||
| cf90f0fb91 |
@ -8,6 +8,10 @@ dependencies {
|
|||||||
// API docs
|
// API docs
|
||||||
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13")
|
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13")
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-cache")
|
||||||
|
implementation("com.github.ben-manes.caffeine:caffeine")
|
||||||
|
|
||||||
// DB
|
// DB
|
||||||
runtimeOnly("com.h2database:h2")
|
runtimeOnly("com.h2database:h2")
|
||||||
runtimeOnly("com.mysql:mysql-connector-j")
|
runtimeOnly("com.mysql:mysql-connector-j")
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package com.sangdol.roomescape
|
|||||||
import org.springframework.boot.Banner
|
import org.springframework.boot.Banner
|
||||||
import org.springframework.boot.SpringApplication
|
import org.springframework.boot.SpringApplication
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
|
import org.springframework.cache.annotation.EnableCaching
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@EnableCaching
|
||||||
@SpringBootApplication(
|
@SpringBootApplication(
|
||||||
scanBasePackages = ["com.sangdol.roomescape", "com.sangdol.common"]
|
scanBasePackages = ["com.sangdol.roomescape", "com.sangdol.common"]
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,22 +3,18 @@ package com.sangdol.roomescape.theme.business
|
|||||||
import com.sangdol.common.persistence.IDGenerator
|
import com.sangdol.common.persistence.IDGenerator
|
||||||
import com.sangdol.roomescape.admin.business.AdminService
|
import com.sangdol.roomescape.admin.business.AdminService
|
||||||
import com.sangdol.roomescape.common.types.AuditingInfo
|
import com.sangdol.roomescape.common.types.AuditingInfo
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeDetailResponse
|
import com.sangdol.roomescape.theme.dto.*
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeSummaryListResponse
|
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeNameListResponse
|
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeCreateRequest
|
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeCreateResponse
|
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeUpdateRequest
|
|
||||||
import com.sangdol.roomescape.theme.exception.ThemeErrorCode
|
import com.sangdol.roomescape.theme.exception.ThemeErrorCode
|
||||||
import com.sangdol.roomescape.theme.exception.ThemeException
|
import com.sangdol.roomescape.theme.exception.ThemeException
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||||
import com.sangdol.roomescape.theme.mapper.toDetailResponse
|
import com.sangdol.roomescape.theme.mapper.toDetailResponse
|
||||||
import com.sangdol.roomescape.theme.mapper.toSummaryListResponse
|
|
||||||
import com.sangdol.roomescape.theme.mapper.toEntity
|
import com.sangdol.roomescape.theme.mapper.toEntity
|
||||||
import com.sangdol.roomescape.theme.mapper.toNameListResponse
|
import com.sangdol.roomescape.theme.mapper.toNameListResponse
|
||||||
|
import com.sangdol.roomescape.theme.mapper.toSummaryListResponse
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.cache.annotation.CacheEvict
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
@ -81,7 +77,7 @@ class AdminThemeService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CacheEvict(cacheNames = ["theme-details"], key = "#id")
|
||||||
@Transactional
|
@Transactional
|
||||||
fun deleteTheme(id: Long) {
|
fun deleteTheme(id: Long) {
|
||||||
log.info { "[deleteTheme] 테마 삭제 시작: id=${id}" }
|
log.info { "[deleteTheme] 테마 삭제 시작: id=${id}" }
|
||||||
@ -93,6 +89,7 @@ class AdminThemeService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CacheEvict(cacheNames = ["theme-details"], key = "#id")
|
||||||
@Transactional
|
@Transactional
|
||||||
fun updateTheme(id: Long, request: ThemeUpdateRequest) {
|
fun updateTheme(id: Long, request: ThemeUpdateRequest) {
|
||||||
log.info { "[updateTheme] 테마 수정 시작: id=${id}, request=${request}" }
|
log.info { "[updateTheme] 테마 수정 시작: id=${id}, request=${request}" }
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import com.sangdol.roomescape.theme.mapper.toInfoResponse
|
|||||||
import com.sangdol.roomescape.theme.mapper.toListResponse
|
import com.sangdol.roomescape.theme.mapper.toListResponse
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry
|
||||||
|
import org.springframework.cache.annotation.Cacheable
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
@ -21,13 +23,19 @@ private val log: KLogger = KotlinLogging.logger {}
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
class ThemeService(
|
class ThemeService(
|
||||||
private val themeRepository: ThemeRepository
|
private val themeRepository: ThemeRepository,
|
||||||
|
meterRegistry: MeterRegistry
|
||||||
) {
|
) {
|
||||||
|
private val themeDetailQueryRequestCount = meterRegistry.counter("theme.detail.query.requested")
|
||||||
|
|
||||||
|
@Cacheable(cacheNames = ["theme-details"], key="#id")
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findInfoById(id: Long): ThemeInfoResponse {
|
fun findInfoById(id: Long): ThemeInfoResponse {
|
||||||
log.info { "[findInfoById] 테마 조회 시작: id=$id" }
|
log.info { "[findInfoById] 테마 조회 시작: id=$id" }
|
||||||
|
|
||||||
val theme = themeRepository.findByIdOrNull(id) ?: run {
|
val theme = themeRepository.findByIdOrNull(id)?.also {
|
||||||
|
themeDetailQueryRequestCount.increment()
|
||||||
|
} ?: run {
|
||||||
log.warn { "[updateTheme] 테마 조회 실패: id=$id" }
|
log.warn { "[updateTheme] 테마 조회 실패: id=$id" }
|
||||||
throw ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
throw ThemeException(ThemeErrorCode.THEME_NOT_FOUND)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,9 @@ spring:
|
|||||||
jdbc:
|
jdbc:
|
||||||
batch_size: ${JDBC_BATCH_SIZE:100}
|
batch_size: ${JDBC_BATCH_SIZE:100}
|
||||||
order_inserts: true
|
order_inserts: true
|
||||||
|
cache:
|
||||||
|
type: caffeine
|
||||||
|
cache-names: ${CACHE_NAMES:theme-details}
|
||||||
|
|
||||||
management:
|
management:
|
||||||
endpoints:
|
endpoints:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package com.sangdol.roomescape.auth.business
|
package com.sangdol.roomescape.auth
|
||||||
|
|
||||||
import com.sangdol.roomescape.auth.business.domain.PrincipalType
|
import com.sangdol.roomescape.auth.business.domain.PrincipalType
|
||||||
import com.sangdol.roomescape.auth.infrastructure.persistence.LoginHistoryEntity
|
import com.sangdol.roomescape.auth.infrastructure.persistence.LoginHistoryEntity
|
||||||
@ -68,4 +68,4 @@ class LoginHistoryEventListenerTest : FunSpec() {
|
|||||||
|
|
||||||
return batch
|
return batch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,12 +1,13 @@
|
|||||||
package com.sangdol.roomescape.reservation.business.event
|
package com.sangdol.roomescape.reservation
|
||||||
|
|
||||||
|
import com.sangdol.roomescape.reservation.business.event.ReservationConfirmEvent
|
||||||
|
import com.sangdol.roomescape.reservation.business.event.ReservationEventListener
|
||||||
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
|
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||||
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
|
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||||
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
|
||||||
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
||||||
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
||||||
import io.kotest.assertions.assertSoftly
|
import io.kotest.assertions.assertSoftly
|
||||||
import io.kotest.core.spec.style.FunSpec
|
|
||||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
@ -42,4 +43,4 @@ class ReservationEventListenerTest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,21 +6,28 @@ import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType
|
|||||||
import com.sangdol.roomescape.auth.exception.AuthErrorCode
|
import com.sangdol.roomescape.auth.exception.AuthErrorCode
|
||||||
import com.sangdol.roomescape.supports.*
|
import com.sangdol.roomescape.supports.*
|
||||||
import com.sangdol.roomescape.supports.ThemeFixture.createRequest
|
import com.sangdol.roomescape.supports.ThemeFixture.createRequest
|
||||||
|
import com.sangdol.roomescape.theme.business.AdminThemeService
|
||||||
import com.sangdol.roomescape.theme.business.MIN_DURATION
|
import com.sangdol.roomescape.theme.business.MIN_DURATION
|
||||||
import com.sangdol.roomescape.theme.business.MIN_PARTICIPANTS
|
import com.sangdol.roomescape.theme.business.MIN_PARTICIPANTS
|
||||||
import com.sangdol.roomescape.theme.business.MIN_PRICE
|
import com.sangdol.roomescape.theme.business.MIN_PRICE
|
||||||
|
import com.sangdol.roomescape.theme.business.ThemeService
|
||||||
|
import com.sangdol.roomescape.theme.dto.ThemeInfoResponse
|
||||||
import com.sangdol.roomescape.theme.exception.ThemeErrorCode
|
import com.sangdol.roomescape.theme.exception.ThemeErrorCode
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||||
import com.sangdol.roomescape.theme.dto.ThemeUpdateRequest
|
import com.sangdol.roomescape.theme.dto.ThemeUpdateRequest
|
||||||
|
import io.kotest.assertions.assertSoftly
|
||||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
|
import org.springframework.cache.CacheManager
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.http.HttpMethod
|
import org.springframework.http.HttpMethod
|
||||||
|
|
||||||
class AdminThemeApiTest(
|
class AdminThemeApiTest(
|
||||||
private val themeRepository: ThemeRepository
|
private val themeRepository: ThemeRepository,
|
||||||
|
private val themeService: ThemeService,
|
||||||
|
private val cacheManager: CacheManager
|
||||||
) : FunSpecSpringbootTest() {
|
) : FunSpecSpringbootTest() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -482,14 +489,19 @@ class AdminThemeApiTest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("정상 삭제") {
|
test("정상 삭제 및 캐시 제거 확인") {
|
||||||
val token = testAuthUtil.defaultHqAdminLogin().second
|
val token = testAuthUtil.defaultHqAdminLogin().second
|
||||||
val createdTheme = initialize("테스트를 위한 테마 생성") {
|
val createdTheme = initialize("테스트를 위한 테마 생성") {
|
||||||
dummyInitializer.createTheme()
|
dummyInitializer.createTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initialize("테마 캐시 추가") {
|
||||||
|
themeService.findInfoById(createdTheme.id)
|
||||||
|
cacheManager.getCache("theme-details")?.get(createdTheme.id, ThemeInfoResponse::class.java).shouldNotBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
token = testAuthUtil.defaultHqAdminLogin().second,
|
token = token,
|
||||||
on = {
|
on = {
|
||||||
delete("/admin/themes/${createdTheme.id}")
|
delete("/admin/themes/${createdTheme.id}")
|
||||||
},
|
},
|
||||||
@ -498,6 +510,7 @@ class AdminThemeApiTest(
|
|||||||
}
|
}
|
||||||
).also {
|
).also {
|
||||||
themeRepository.findByIdOrNull(createdTheme.id) shouldBe null
|
themeRepository.findByIdOrNull(createdTheme.id) shouldBe null
|
||||||
|
cacheManager.getCache("theme-details")?.get(createdTheme.id, ThemeInfoResponse::class.java) shouldBe null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,7 +579,7 @@ class AdminThemeApiTest(
|
|||||||
|
|
||||||
val updateRequest = ThemeUpdateRequest(name = "modified")
|
val updateRequest = ThemeUpdateRequest(name = "modified")
|
||||||
|
|
||||||
test("정상 수정 및 감사 정보 변경 확인") {
|
test("정상 수정 및 감사 정보 & 캐시 변경 확인") {
|
||||||
val createdThemeId: Long = initialize("테스트를 위한 관리자1의 테마 생성") {
|
val createdThemeId: Long = initialize("테스트를 위한 관리자1의 테마 생성") {
|
||||||
runTest(
|
runTest(
|
||||||
token = testAuthUtil.defaultHqAdminLogin().second,
|
token = testAuthUtil.defaultHqAdminLogin().second,
|
||||||
@ -582,6 +595,11 @@ class AdminThemeApiTest(
|
|||||||
).extract().path("data.id")
|
).extract().path("data.id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initialize("테마 캐시 추가") {
|
||||||
|
themeService.findInfoById(createdThemeId)
|
||||||
|
cacheManager.getCache("theme-details")?.get(createdThemeId, ThemeInfoResponse::class.java).shouldNotBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
val (otherAdmin, otherAdminToken) = initialize("감사 정보 변경 확인을 위한 관리자2 로그인") {
|
val (otherAdmin, otherAdminToken) = initialize("감사 정보 변경 확인을 위한 관리자2 로그인") {
|
||||||
testAuthUtil.adminLogin(
|
testAuthUtil.adminLogin(
|
||||||
AdminFixture.createHqAdmin(permissionLevel = AdminPermissionLevel.WRITABLE)
|
AdminFixture.createHqAdmin(permissionLevel = AdminPermissionLevel.WRITABLE)
|
||||||
@ -604,6 +622,12 @@ class AdminThemeApiTest(
|
|||||||
|
|
||||||
updatedTheme.name shouldBe updateRequest.name
|
updatedTheme.name shouldBe updateRequest.name
|
||||||
updatedTheme.updatedBy shouldBe otherAdmin.id
|
updatedTheme.updatedBy shouldBe otherAdmin.id
|
||||||
|
|
||||||
|
|
||||||
|
// 캐시 제거 확인
|
||||||
|
assertSoftly(cacheManager.getCache("theme-details")?.get(createdThemeId, ThemeInfoResponse::class.java)) {
|
||||||
|
this shouldBe null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,23 +10,32 @@ import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
|||||||
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||||
import com.sangdol.roomescape.theme.mapper.toEntity
|
import com.sangdol.roomescape.theme.mapper.toEntity
|
||||||
import com.sangdol.roomescape.user.infrastructure.persistence.UserEntity
|
import com.sangdol.roomescape.user.infrastructure.persistence.UserEntity
|
||||||
|
import io.kotest.assertions.assertSoftly
|
||||||
import io.kotest.matchers.collections.shouldContainInOrder
|
import io.kotest.matchers.collections.shouldContainInOrder
|
||||||
import io.kotest.matchers.collections.shouldHaveSize
|
import io.kotest.matchers.collections.shouldHaveSize
|
||||||
import io.kotest.matchers.comparables.shouldBeLessThan
|
import io.kotest.matchers.comparables.shouldBeLessThan
|
||||||
|
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
|
import org.springframework.cache.CacheManager
|
||||||
import org.springframework.http.HttpMethod
|
import org.springframework.http.HttpMethod
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
class ThemeApiTest(
|
class ThemeApiTest(
|
||||||
private val themeRepository: ThemeRepository
|
private val themeRepository: ThemeRepository,
|
||||||
|
private val cacheManager: CacheManager
|
||||||
) : FunSpecSpringbootTest() {
|
) : FunSpecSpringbootTest() {
|
||||||
init {
|
init {
|
||||||
context("ID로 테마 정보를 조회한다.") {
|
context("ID로 테마 정보를 조회한다.") {
|
||||||
test("정상 응답") {
|
test("정상 응답 및 캐시 저장 확인") {
|
||||||
val createdTheme: ThemeEntity = initialize("조회를 위한 테마 생성") {
|
val createdTheme: ThemeEntity = initialize("조회를 위한 테마 생성") {
|
||||||
dummyInitializer.createTheme()
|
dummyInitializer.createTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheManager.getCache("theme-details")?.get(createdTheme.id, ThemeInfoResponse::class.java).also {
|
||||||
|
it shouldBe null
|
||||||
|
}
|
||||||
|
|
||||||
runTest(
|
runTest(
|
||||||
on = {
|
on = {
|
||||||
get("/themes/${createdTheme.id}")
|
get("/themes/${createdTheme.id}")
|
||||||
@ -43,6 +52,15 @@ class ThemeApiTest(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assertSoftly(cacheManager.getCache("theme-details")) {
|
||||||
|
this.shouldNotBeNull()
|
||||||
|
|
||||||
|
val themeFromCache = this.get(createdTheme.id, ThemeInfoResponse::class.java)
|
||||||
|
|
||||||
|
themeFromCache.shouldNotBeNull()
|
||||||
|
themeFromCache.id shouldBe createdTheme.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("테마가 없으면 실패한다.") {
|
test("테마가 없으면 실패한다.") {
|
||||||
|
|||||||
@ -0,0 +1,68 @@
|
|||||||
|
package com.sangdol.roomescape.theme
|
||||||
|
|
||||||
|
import com.ninjasquad.springmockk.MockkBean
|
||||||
|
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
||||||
|
import com.sangdol.roomescape.supports.IDGenerator
|
||||||
|
import com.sangdol.roomescape.supports.initialize
|
||||||
|
import com.sangdol.roomescape.theme.business.ThemeService
|
||||||
|
import com.sangdol.roomescape.theme.infrastructure.persistence.Difficulty
|
||||||
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
|
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
class ThemeConcurrencyTest(
|
||||||
|
private val themeService: ThemeService,
|
||||||
|
@MockkBean private val themeRepository: ThemeRepository,
|
||||||
|
) : FunSpecSpringbootTest() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
test("동일한 테마에 대한 반복 조회 요청시, DB 요청은 1회만 발생한다.") {
|
||||||
|
val entity = ThemeEntity(
|
||||||
|
id = IDGenerator.create(),
|
||||||
|
name = "테스트입니다.",
|
||||||
|
description = "테스트에요!",
|
||||||
|
thumbnailUrl = "http://localhost:8080/hello",
|
||||||
|
difficulty = Difficulty.VERY_EASY,
|
||||||
|
price = 10000,
|
||||||
|
minParticipants = 3,
|
||||||
|
maxParticipants = 5,
|
||||||
|
availableMinutes = 90,
|
||||||
|
expectedMinutesFrom = 70,
|
||||||
|
expectedMinutesTo = 80,
|
||||||
|
isActive = true
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
every {
|
||||||
|
themeRepository.findByIdOrNull(entity.id)
|
||||||
|
} returns entity
|
||||||
|
|
||||||
|
initialize("캐시 등록") {
|
||||||
|
themeService.findInfoById(entity.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
val requestCount = 64
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val latch = CountDownLatch(requestCount)
|
||||||
|
|
||||||
|
(1..requestCount).map {
|
||||||
|
async {
|
||||||
|
latch.countDown()
|
||||||
|
latch.await()
|
||||||
|
themeService.findInfoById(entity.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
themeRepository.findByIdOrNull(entity.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,9 @@ spring:
|
|||||||
init:
|
init:
|
||||||
mode: always
|
mode: always
|
||||||
schema-locations: classpath:schema/schema-mysql.sql
|
schema-locations: classpath:schema/schema-mysql.sql
|
||||||
|
cache:
|
||||||
|
type: caffeine
|
||||||
|
cache-names: ${CACHE_NAMES:theme-details}
|
||||||
|
|
||||||
security:
|
security:
|
||||||
jwt:
|
jwt:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user