Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/ci-backend.yml → .github/workflows/ci-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ jobs:
build:
runs-on: ubuntu-latest

services:
redis:
image: redis:7.0.8-alpine
ports:
- 6379:6379

steps:
- uses: actions/checkout@v4

Expand All @@ -24,5 +30,8 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build with Gradle
run: ./gradlew clean build
- name: Run tests with Gradle
run: ./gradlew clean test
env:
SPRING_REDIS_HOST: localhost
SPRING_REDIS_PORT: 6379
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ dependencies {
testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.2")

testImplementation("io.mockk:mockk:1.13.10")

testImplementation("org.testcontainers:testcontainers:1.19.3")
testImplementation("org.testcontainers:junit-jupiter:1.19.3")
}

dependencyManagement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import com.dobby.backend.application.usecase.experiment.GetMyExperimentPostsUseC
import com.dobby.backend.domain.exception.ExperimentAreaInCorrectException
import com.dobby.backend.domain.exception.ExperimentAreaOverflowException
import com.dobby.backend.domain.exception.InvalidRequestValueException
import com.dobby.backend.domain.gateway.CacheGateway
import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Area
import com.dobby.backend.infrastructure.database.entity.enums.experiment.RecruitStatus
import com.fasterxml.jackson.databind.ObjectMapper
import jakarta.transaction.Transactional
import org.springframework.stereotype.Service

Expand All @@ -30,9 +33,12 @@ class ExperimentPostService(
private val getExperimentPostTotalCountByCustomFilterUseCase: GetExperimentPostTotalCountByCustomFilterUseCase,
private val getMyExperimentPostsUseCase: GetMyExperimentPostsUseCase,
private val getMyExperimentPostTotalCountUseCase: GetMyExperimentPostTotalCountUseCase,
private val cacheGateway: CacheGateway,
private val objectMapper: ObjectMapper,
) {
@Transactional
fun createNewExperimentPost(input: CreateExperimentPostUseCase.Input): CreateExperimentPostUseCase.Output {
evictExperimentPostCountsCaches()
return createExperimentPostUseCase.execute(input)
}

Expand Down Expand Up @@ -69,14 +75,36 @@ class ExperimentPostService(
}

fun getExperimentPostCounts(input: Any): Any {
return when (input) {
if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) {
getCachedExperimentPostCounts(input.recruitStatus)?.let { return it }
}

val output = when (input) {
Comment on lines 77 to +82
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재는 전체 지역의 공고 개수 조회 (region == null)가 가장 빈번하게 호출될 것으로 예상되어 이 경우에만 캐싱을 적용했습니다. 만약 특정 지역에 대한 조회 요청도 증가하는 것이 확인된다면, 해당 부분에 대한 캐싱 정책도 추가로 검토할 예정입니다~

is GetExperimentPostCountsByRegionUseCase.Input -> getExperimentPostCountsByRegionUseCase.execute(input)
is GetExperimentPostCountsByAreaUseCase.Input -> getExperimentPostCountsByAreaUseCase.execute(input)
else -> throw IllegalArgumentException("Invalid input type: ${input::class.java.simpleName}")
}

if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) {
cacheExperimentPostCounts(input.recruitStatus, output)
}

return output
}
Comment on lines 77 to +93
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

ObjectMapper 예외 처리가 필요합니다.

getExperimentPostCounts 메서드에서 캐시 저장 시 ObjectMapper.writeValueAsString() 호출에 대한 예외 처리가 누락되었습니다.

다음과 같이 예외 처리를 추가해주세요:

 if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) {
-    cacheExperimentPostCounts(input.recruitStatus, output)
+    try {
+        cacheExperimentPostCounts(input.recruitStatus, output)
+    } catch (e: Exception) {
+        log.error("Failed to cache experiment post counts: ${e.message}")
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun getExperimentPostCounts(input: Any): Any {
return when (input) {
if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) {
getCachedExperimentPostCounts(input.recruitStatus)?.let { return it }
}
val output = when (input) {
is GetExperimentPostCountsByRegionUseCase.Input -> getExperimentPostCountsByRegionUseCase.execute(input)
is GetExperimentPostCountsByAreaUseCase.Input -> getExperimentPostCountsByAreaUseCase.execute(input)
else -> throw IllegalArgumentException("Invalid input type: ${input::class.java.simpleName}")
}
if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) {
cacheExperimentPostCounts(input.recruitStatus, output)
}
return output
}
fun getExperimentPostCounts(input: Any): Any {
if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) {
getCachedExperimentPostCounts(input.recruitStatus)?.let { return it }
}
val output = when (input) {
is GetExperimentPostCountsByRegionUseCase.Input -> getExperimentPostCountsByRegionUseCase.execute(input)
is GetExperimentPostCountsByAreaUseCase.Input -> getExperimentPostCountsByAreaUseCase.execute(input)
else -> throw IllegalArgumentException("Invalid input type: ${input::class.java.simpleName}")
}
if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) {
try {
cacheExperimentPostCounts(input.recruitStatus, output)
} catch (e: Exception) {
log.error("Failed to cache experiment post counts: ${e.message}")
}
}
return output
}


private fun getCachedExperimentPostCounts(recruitStatus: RecruitStatus): GetExperimentPostCountsByRegionUseCase.Output? {
val cacheKey = "experimentPostCounts:$recruitStatus"
return cacheGateway.get(cacheKey)?.let {
objectMapper.readValue(it, GetExperimentPostCountsByRegionUseCase.Output::class.java)
}
}

private fun cacheExperimentPostCounts(recruitStatus: RecruitStatus, output: Any) {
val cacheKey = "experimentPostCounts:$recruitStatus"
cacheGateway.set(cacheKey, objectMapper.writeValueAsString(output))
}
Comment on lines +95 to 105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

직렬화/역직렬화 예외 처리가 필요합니다.

ObjectMapper 작업 시 발생할 수 있는 예외에 대한 처리가 필요합니다.

 private fun getCachedExperimentPostCounts(recruitStatus: RecruitStatus): GetExperimentPostCountsByRegionUseCase.Output? {
     val cacheKey = "experimentPostCounts:$recruitStatus"
     return cacheGateway.get(cacheKey)?.let {
+        try {
             objectMapper.readValue(it, GetExperimentPostCountsByRegionUseCase.Output::class.java)
+        } catch (e: Exception) {
+            log.error("Failed to deserialize cached value: ${e.message}")
+            null
+        }
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private fun getCachedExperimentPostCounts(recruitStatus: RecruitStatus): GetExperimentPostCountsByRegionUseCase.Output? {
val cacheKey = "experimentPostCounts:$recruitStatus"
return cacheGateway.get(cacheKey)?.let {
objectMapper.readValue(it, GetExperimentPostCountsByRegionUseCase.Output::class.java)
}
}
private fun cacheExperimentPostCounts(recruitStatus: RecruitStatus, output: Any) {
val cacheKey = "experimentPostCounts:$recruitStatus"
cacheGateway.set(cacheKey, objectMapper.writeValueAsString(output))
}
private fun getCachedExperimentPostCounts(recruitStatus: RecruitStatus): GetExperimentPostCountsByRegionUseCase.Output? {
val cacheKey = "experimentPostCounts:$recruitStatus"
return cacheGateway.get(cacheKey)?.let {
try {
objectMapper.readValue(it, GetExperimentPostCountsByRegionUseCase.Output::class.java)
} catch (e: Exception) {
log.error("Failed to deserialize cached value: ${e.message}")
null
}
}
}
private fun cacheExperimentPostCounts(recruitStatus: RecruitStatus, output: Any) {
val cacheKey = "experimentPostCounts:$recruitStatus"
cacheGateway.set(cacheKey, objectMapper.writeValueAsString(output))
}


fun validateFilter(input: GetExperimentPostsUseCase.Input) {
private fun validateFilter(input: GetExperimentPostsUseCase.Input) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 제가 이 부분 놓친 것 같은데 바꾸어주셔서 감사합니다! 🙇‍♀️

val locationInfo = input.customFilter.locationTarget ?: return

locationInfo.areas?.let { validateLocationAreaCount(it) }
Expand Down Expand Up @@ -122,6 +150,12 @@ class ExperimentPostService(

@Transactional
fun deleteExperimentPost(input: DeleteExperimentPostUseCase.Input) {
evictExperimentPostCountsCaches()
deleteExperimentPostUseCase.execute(input)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 캐시 삭제를 먼저 하고 👉 DB에서 삭제하는 로직으로 받아들였습니다.
그런데 deleteExperimentPostUseCase.execute 시에 예외가 발생한다면 캐시는 삭제됐는데 DB에서는 삭제되지 않는 상황이 발생할 수도 있지 않나...! 싶습니다.
DB에서 삭제를 하고 👉 캐시 삭제 순으로 해야 DB 삭제 시에 예외가 발생하더라도, 캐시가 남아있기 때문에 어느정도 일관성이 유지될 수 있지 않나 생각이 듭니다🤔
지수님 생각은 어떠실지 궁금합니다.

Copy link
Member Author

@Ji-soo708 Ji-soo708 Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정님 말이 맞습니다. 먼저 DB에서 데이터를 삭제하고 캐시를 무효화시키는 흐름이 맞는데 이 부분을 놓쳤네요. 꼼꼼한 리뷰 감사합니다! 👍


private fun evictExperimentPostCountsCaches() {
cacheGateway.evict("experimentPostCounts:ALL")
cacheGateway.evict("experimentPostCounts:OPEN")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

캐시 키를 List으로 관리하는 게 어떨까 제안드려요!

private fun evictExperimentPostCountsCaches() {
    listOf("ALL", "OPEN").forEach { cacheGateway.evict("experimentPostCounts:$it") }
}

이렇게 되면 추후에 다른 지역을 기준으로 공고를 조회하는 키가 추가되더라도, 유지보수성이 좋아질 수 있을 거라 기대되는데 지수님 의견은 어떠신지요? 😊🤔

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.dobby.backend.domain.gateway

interface CacheGateway {
fun get(key: String): String?
fun set(key: String, value: String)
fun evict(key: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.dobby.backend.infrastructure.gateway.cache

import com.dobby.backend.domain.gateway.CacheGateway
import org.springframework.core.env.Environment
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Component
import java.util.concurrent.TimeUnit

@Component
class RedisCacheGatewayImpl(
private val redisTemplate: RedisTemplate<String, String>,
private val environment: Environment
) : CacheGateway {

private val cacheTimeout = 240L

override fun get(key: String): String? {
return redisTemplate.opsForValue().get(getCacheKey(key))
}
Comment on lines +17 to +19
Copy link

@coderabbitai coderabbitai bot Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Redis 작업 실패 시 예외 처리 추가 필요

Redis 작업 실패 시 적절한 예외 처리를 추가하면 장애 상황에서도 안정적으로 동작할 수 있습니다.

     override fun get(key: String): String? {
-        return redisTemplate.opsForValue().get(getCacheKey(key))
+        return try {
+            redisTemplate.opsForValue().get(getCacheKey(key))
+        } catch (e: Exception) {
+            logger.error("캐시 조회 실패. key={}", key, e)
+            null
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override fun get(key: String): String? {
return redisTemplate.opsForValue().get(getCacheKey(key))
}
override fun get(key: String): String? {
return try {
redisTemplate.opsForValue().get(getCacheKey(key))
} catch (e: Exception) {
logger.error("캐시 조회 실패. key={}", key, e)
null
}
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

라비따이야, 레디스에 캐시가 없을 수도 있지. 안그러니?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ji-soo708 네, 말씀하신대로입니다. 레디스에 캐시가 없는 것은 정상적인 상황이므로 현재 구현처럼 단순히 null을 반환하는 것이 더 적절합니다. 제가 제안했던 에러 로깅은 불필요했네요. 이전 제안을 철회하겠습니다.


✏️ Learnings added
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#92
File: src/main/kotlin/com/dobby/backend/infrastructure/gateway/cache/RedisCacheGatewayImpl.kt:17-19
Timestamp: 2025-02-10T11:52:40.151Z
Learning: Redis cache operations that return null (like get) should not log errors as cache misses are part of normal operation. The null return value already appropriately handles both cache misses and error cases.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


override fun set(key: String, value: String) {
redisTemplate.opsForValue().set(getCacheKey(key), value, cacheTimeout, TimeUnit.MINUTES)
}

override fun evict(key: String) {
redisTemplate.delete(getCacheKey(key))
}

private fun getCacheKey(key: String): String {
val activeProfile = environment.activeProfiles.firstOrNull() ?: "local"
return "$activeProfile:$key"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package com.dobby.backend.application.service

import com.dobby.backend.application.usecase.experiment.CreateExperimentPostUseCase
import com.dobby.backend.application.usecase.experiment.GetExperimentPostCountsByRegionUseCase
import com.dobby.backend.config.RedisTestContainer
import com.dobby.backend.domain.gateway.CacheGateway
import com.dobby.backend.infrastructure.database.entity.enums.MatchType
import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Area
import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Region
import com.dobby.backend.infrastructure.database.entity.enums.experiment.RecruitStatus
import com.dobby.backend.infrastructure.database.entity.enums.experiment.TimeSlot
import com.dobby.backend.infrastructure.database.entity.enums.member.GenderType
import com.dobby.backend.infrastructure.database.entity.enums.member.MemberStatus
import com.dobby.backend.infrastructure.database.entity.enums.member.ProviderType
import com.dobby.backend.infrastructure.database.entity.enums.member.RoleType
import com.dobby.backend.infrastructure.database.entity.member.MemberEntity
import com.dobby.backend.infrastructure.database.repository.MemberRepository
import com.fasterxml.jackson.databind.ObjectMapper
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.nulls.shouldBeNull
import io.mockk.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import java.time.LocalDate
import java.time.LocalDateTime

@SpringBootTest
@ActiveProfiles("test")
class ExperimentPostServiceTest @Autowired constructor(
private val experimentPostService: ExperimentPostService,
private val memberRepository: MemberRepository,
private val cacheGateway: CacheGateway,
private val objectMapper: ObjectMapper
) : BehaviorSpec({

val createExperimentPostUseCase = mockk<CreateExperimentPostUseCase>()
lateinit var memberId: String

listeners(RedisTestContainer)

beforeSpec {
clearAllMocks()

val savedMember = memberRepository.save(
MemberEntity(
id = "1",
oauthEmail = "[email protected]",
contactEmail = "[email protected]",
provider = ProviderType.GOOGLE,
status = MemberStatus.ACTIVE,
role = RoleType.RESEARCHER,
name = "테스트",
createdAt = LocalDateTime.now(),
updatedAt = LocalDateTime.now(),
deletedAt = null
)
)
memberId = savedMember.id
}

given("Redis 캐시에 지역별 실험 공고 개수를 저장할 때") {
val input = GetExperimentPostCountsByRegionUseCase.Input(region = null, recruitStatus = RecruitStatus.ALL)
val output = GetExperimentPostCountsByRegionUseCase.Output(total = 100, area = emptyList())

val cacheKey = "experimentPostCounts:ALL"

`when`("캐시에 데이터를 저장하고 조회하면") {
cacheGateway.set(cacheKey, objectMapper.writeValueAsString(output))
val cachedData = cacheGateway.get(cacheKey)

then("저장된 데이터가 올바르게 반환되어야 한다") {
cachedData shouldBe objectMapper.writeValueAsString(output)
}
}
}

given("새로운 실험 공고를 생성할 때") {
val cacheKey = "experimentPostCounts:ALL"
cacheGateway.set(cacheKey, objectMapper.writeValueAsString(GetExperimentPostCountsByRegionUseCase.Output(100, emptyList())))

val mockInput = mockk<CreateExperimentPostUseCase.Input>(relaxed = true)

every { mockInput.memberId } returns memberId
every { mockInput.leadResearcher } returns "야뿌 연구 리더"
every { mockInput.univName } returns "야뿌대학교"
every { mockInput.region } returns Region.SEOUL
every { mockInput.area } returns Area.GWANAKGU
every { mockInput.detailedAddress } returns "야뿌 연구소"
every { mockInput.reward } returns "Gift Card"
every { mockInput.title } returns "새로운 실험"
every { mockInput.content } returns "참여해 참여해"
every { mockInput.alarmAgree } returns true
every { mockInput.startDate } returns LocalDate.of(2024, 6, 1)
every { mockInput.endDate } returns LocalDate.of(2024, 6, 30)
every { mockInput.matchType } returns MatchType.OFFLINE
every { mockInput.count } returns 3
every { mockInput.timeRequired } returns TimeSlot.ABOUT_1H

val mockTargetGroupInfo = mockk<CreateExperimentPostUseCase.TargetGroupInfo>(relaxed = true)
every { mockTargetGroupInfo.startAge } returns 20
every { mockTargetGroupInfo.endAge } returns 30
every { mockTargetGroupInfo.genderType } returns GenderType.MALE
every { mockTargetGroupInfo.otherCondition } returns "Healthy"

every { mockInput.targetGroupInfo } returns mockTargetGroupInfo

val mockApplyMethodInfo = mockk<CreateExperimentPostUseCase.ApplyMethodInfo>(relaxed = true)
every { mockApplyMethodInfo.content } returns "이메일로 연락주세요"
every { mockApplyMethodInfo.formUrl } returns "https://test.com/apply"
every { mockApplyMethodInfo.phoneNum } returns "010-1234-5678"

every { mockInput.applyMethodInfo } returns mockApplyMethodInfo

val mockImageListInfo = mockk<CreateExperimentPostUseCase.ImageListInfo>(relaxed = true)
every { mockImageListInfo.images } returns listOf("https://test.com/image1.jpg", "https://test.com/image2.jpg")
every { mockInput.imageListInfo } returns mockImageListInfo

every { createExperimentPostUseCase.execute(any()) } returns mockk()

`when`("게시글을 생성하면") {
experimentPostService.createNewExperimentPost(mockInput)

then("캐시가 삭제되어야 한다") {
val cachedData = cacheGateway.get(cacheKey)
cachedData.shouldBeNull()
}
}
}
})
32 changes: 32 additions & 0 deletions src/test/kotlin/com/dobby/backend/config/RedisTestContainer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.dobby.backend.config

import io.kotest.core.listeners.TestListener
import io.kotest.core.spec.Spec
import org.testcontainers.containers.GenericContainer
import org.testcontainers.utility.DockerImageName

object RedisTestContainer : TestListener {
private val isCI = System.getenv("GITHUB_ACTIONS") == "true"

private val redis: GenericContainer<*> = GenericContainer(DockerImageName.parse("redis:7.0.8-alpine")).apply {
withExposedPorts(6379)

if (!isCI) {
portBindings = listOf("6379:6379")
}
}

override suspend fun beforeSpec(spec: Spec) {
redis.start()

val redisHost = "127.0.0.1"
val redisPort = redis.getMappedPort(6379).toString()

System.setProperty("spring.data.redis.host", redisHost)
System.setProperty("spring.data.redis.port", redisPort)
}

override suspend fun afterSpec(spec: Spec) {
redis.stop()
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 테스트 컨테이너가 테스트를 실행할 때마다 매번 레디스 컨테이너를 새로 띄우는 코드로 보여집니다.
앞선 커밋에서는 isRunning 체크로 개선하신 것 같은데, 이를 철회하고 해당 코드를 확정지으신 이유가 궁금합니다 🙋‍♀️

제 생각에는
✅ 컨테이너 재사용을 활성화
✅ beforeSpec에서 기존에 isRunning 체크방식으로 컨테이너 중복실행 방지
✅ afterSpec에서 CI 환경에서는 컨테이너를 자동으로 정리해주는 기능이 있으니, if(!isCI) 로 로컬 환경에서만 컨테이너 정리

식으로 개선할 수 있을 것 같은데, 지수님 의견이 듣고 싶어요 🤔🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

옹 좋은 의견입니다. 👍

일단 Redis 컨테이너를 매번 새로 띄우는 방식으로 변경한 이유는, 현재 단계에서는 Redis를 사용하는 서비스 테스트 코드가 하나라 한 번만 컨테이너가 실행되기 때문에 성능이나 리소스 측면에서 큰 부담이 없기 때문입니다. 또한, 트러블슈팅을 진행하면서 Redis 컨테이너를 매번 새로 띄우면 항상 깨끗한 상태에서 테스트가 실행되므로, 현재 코드가 더 안정적이라고 판단했습니다.

현재는 Redis를 사용하는 테스트가 하나뿐이고, 앞으로도 크게 늘어날 것 같지는 않아서 수정님 코드로 진행해도 문제가 없을 것 같습니다.
따라서, 우선 개선된 코드로 적용하고, 이후 문제가 발생하면 롤백하는 방식이 더 좋을 것 같아요~

Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import com.dobby.backend.infrastructure.config.properties.S3Properties
import io.kotest.core.spec.style.BehaviorSpec
import io.mockk.*
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import java.net.URL
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull

@SpringBootTest
@ActiveProfiles("test")
class S3PreSignedUrlProviderTest : BehaviorSpec({

val amazonS3Client = mockk<AmazonS3>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
spring:
datasource:
url: jdbc:h2:~/dobby;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;
url: jdbc:h2:mem:dobby;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driver-class-name: org.h2.Driver
username: sa
password: ''
jpa:
Expand All @@ -12,8 +13,7 @@ spring:
enabled: true
show_sql: false
format_sql: false
dialect: org.hibernate.dialect.MySQL8Dialect
database-platform: org.hibernate.dialect.MySQL8Dialect
dialect: org.hibernate.dialect.H2Dialect
security:
oauth2:
client:
Expand All @@ -34,7 +34,7 @@ spring:
enable: true
data:
redis:
host: localhost
host: 127.0.0.1
port: 6379

app:
Expand Down
Loading