Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ class BloomingFinder(

suspend fun readRecentlyBloomingByCafeId(cafeId: Long): List<Blooming> = bloomingRepository.findRecentlyByCafeId(cafeId)

fun recentlyBloomingBySpotIds(spotIds: List<Long>): List<Blooming> = bloomingRepository.findRecentBySpotIds(spotIds)
suspend fun recentlyBloomingBySpotIds(spotIds: List<Long>): List<Blooming> = bloomingRepository.findRecentBySpotIds(spotIds)

fun recentlyBloomingByEventIds(eventIds: List<Long>): List<Blooming> = bloomingRepository.findRecentByEventIds(eventIds)
suspend fun recentlyBloomingByEventIds(eventIds: List<Long>): List<Blooming> = bloomingRepository.findRecentByEventIds(eventIds)

fun recentlyBloomingByCafeIds(cafeIds: List<Long>): List<Blooming> = bloomingRepository.findRecentByCafeIds(cafeIds)
suspend fun recentlyBloomingByCafeIds(cafeIds: List<Long>): List<Blooming> = bloomingRepository.findRecentByCafeIds(cafeIds)

fun readTodayBloomingByUserId(
userId: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ interface BloomingRepository {

suspend fun findRecentlyByCafeId(cafeId: Long): List<Blooming>

fun findRecentBySpotIds(spotIds: List<Long>): List<Blooming>
suspend fun findRecentBySpotIds(spotIds: List<Long>): List<Blooming>

fun findRecentByEventIds(eventIds: List<Long>): List<Blooming>
suspend fun findRecentByEventIds(eventIds: List<Long>): List<Blooming>

fun findRecentByCafeIds(cafeIds: List<Long>): List<Blooming>
suspend fun findRecentByCafeIds(cafeIds: List<Long>): List<Blooming>

fun findTodayBloomingByUserId(
userId: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.pida.blooming

import com.fasterxml.jackson.core.type.TypeReference
import com.pida.support.cache.Cache
import com.pida.support.cache.CacheRepository
import com.pida.support.error.ErrorException
import com.pida.support.error.ErrorType
import org.springframework.stereotype.Service
Expand All @@ -12,15 +9,7 @@ class BloomingService(
private val bloomingAppender: BloomingAppender,
private val bloomingValidator: BloomingValidator,
private val bloomingFinder: BloomingFinder,
private val cacheRepository: CacheRepository,
) {
companion object {
const val BLOOMING_SPOT_KEY = "blooming:spot"
const val BLOOMING_EVENT_KEY = "blooming:event"
const val BLOOMING_CAFE_KEY = "blooming:cafe"
const val BLOOMING_TTL = 30L
}

suspend fun add(newBlooming: NewBlooming): Blooming {
val blooming =
when (newBlooming) {
Expand All @@ -38,9 +27,7 @@ class BloomingService(
}
bloomingValidator.addValidate(blooming)

val result = bloomingAppender.add(newBlooming)
evictBloomingCache(newBlooming)
return result
return bloomingAppender.add(newBlooming)
}

suspend fun recentlyBloomingBySpotId(spotId: Long): List<Blooming> = bloomingFinder.readRecentlyBloomingBySpotId(spotId)
Expand All @@ -49,41 +36,14 @@ class BloomingService(

suspend fun recentlyBloomingByCafeId(cafeId: Long): List<Blooming> = bloomingFinder.readRecentlyBloomingByCafeId(cafeId)

fun recentlyBloomingBySpotIds(spotIds: List<Long>): List<Blooming> =
spotIds.flatMap { spotId -> cachedRecentlyBloomingBySpotId(spotId) }

fun recentlyBloomingByEventIds(eventIds: List<Long>): List<Blooming> =
eventIds.flatMap { eventId -> cachedRecentlyBloomingByEventId(eventId) }

fun recentlyBloomingByCafeIds(cafeIds: List<Long>): List<Blooming> =
cafeIds.flatMap { cafeId -> cachedRecentlyBloomingByCafeId(cafeId) }
suspend fun recentlyBloomingBySpotIds(spotIds: List<Long>): List<Blooming> =
bloomingFinder.recentlyBloomingBySpotIds(spotIds.distinct())

private fun cachedRecentlyBloomingBySpotId(spotId: Long): List<Blooming> =
Cache.cacheBlocking(
ttl = BLOOMING_TTL,
key = "$BLOOMING_SPOT_KEY:$spotId",
typeReference = object : TypeReference<List<Blooming>>() {},
) {
bloomingFinder.recentlyBloomingBySpotIds(listOf(spotId))
}
suspend fun recentlyBloomingByEventIds(eventIds: List<Long>): List<Blooming> =
bloomingFinder.recentlyBloomingByEventIds(eventIds.distinct())

private fun cachedRecentlyBloomingByEventId(eventId: Long): List<Blooming> =
Cache.cacheBlocking(
ttl = BLOOMING_TTL,
key = "$BLOOMING_EVENT_KEY:$eventId",
typeReference = object : TypeReference<List<Blooming>>() {},
) {
bloomingFinder.recentlyBloomingByEventIds(listOf(eventId))
}

private fun cachedRecentlyBloomingByCafeId(cafeId: Long): List<Blooming> =
Cache.cacheBlocking(
ttl = BLOOMING_TTL,
key = "$BLOOMING_CAFE_KEY:$cafeId",
typeReference = object : TypeReference<List<Blooming>>() {},
) {
bloomingFinder.recentlyBloomingByCafeIds(listOf(cafeId))
}
suspend fun recentlyBloomingByCafeIds(cafeIds: List<Long>): List<Blooming> =
bloomingFinder.recentlyBloomingByCafeIds(cafeIds.distinct())

fun verifyTodayBlooming(
userId: Long,
Expand All @@ -104,12 +64,4 @@ class BloomingService(

return bloomingValidator.todayBloomingValidate(blooming)
}

private fun evictBloomingCache(newBlooming: NewBlooming) {
when (newBlooming) {
is NewBlooming.FlowerSpot -> cacheRepository.delete("$BLOOMING_SPOT_KEY:${newBlooming.flowerSpotId}")
is NewBlooming.FlowerEvent -> cacheRepository.delete("$BLOOMING_EVENT_KEY:${newBlooming.flowerEventId}")
is NewBlooming.FlowerSpotCafe -> cacheRepository.delete("$BLOOMING_CAFE_KEY:${newBlooming.flowerSpotCafeId}")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.pida.blooming

import com.pida.support.cache.CacheRepository
import com.pida.support.error.ErrorException
import com.pida.support.error.ErrorType
import io.kotest.matchers.shouldBe
Expand All @@ -17,8 +16,7 @@ class BloomingServiceTest {
val bloomingAppender = mockk<BloomingAppender>()
val bloomingValidator = mockk<BloomingValidator>()
val bloomingFinder = mockk<BloomingFinder>()
val cacheRepository = mockk<CacheRepository>(relaxed = true)
val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder, cacheRepository)
val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder)
val blooming =
Blooming(
id = 1L,
Expand All @@ -44,13 +42,93 @@ class BloomingServiceTest {
verify { bloomingValidator.todayBloomingValidate(blooming) }
}

@Test
fun `spot batch μ‘°νšŒλŠ” 쀑볡 idλ₯Ό μ œκ±°ν•œ λ’€ ν•œ 번의 finder 호좜둜 μœ„μž„ν•œλ‹€`() {
val bloomingAppender = mockk<BloomingAppender>()
val bloomingValidator = mockk<BloomingValidator>()
val bloomingFinder = mockk<BloomingFinder>()
val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder)
val bloomings =
listOf(
Blooming(
id = 1L,
status = BloomingStatus.BLOOMED,
userId = 10L,
flowerSpotId = 30L,
flowerEventId = null,
flowerSpotCafeId = null,
createdAt = LocalDateTime.of(2026, 4, 1, 9, 0),
),
)

every { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) } returns bloomings

val result = service.recentlyBloomingBySpotIds(listOf(30L, 31L, 30L))

result shouldBe bloomings
verify(exactly = 1) { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) }
Comment on lines +64 to +69
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Tests for suspend functions require coEvery and runTest.

The recentlyBloomingBySpotIds is now a suspend function, but the test uses every instead of coEvery and doesn't run within a coroutine context. This will cause compilation errors or runtime issues.

πŸ”§ Proposed fix for coroutine testing
+import io.mockk.coEvery
+import io.mockk.coVerify
+import kotlinx.coroutines.test.runTest

 `@Test`
-fun `spot batch μ‘°νšŒλŠ” 쀑볡 idλ₯Ό μ œκ±°ν•œ λ’€ ν•œ 번의 finder 호좜둜 μœ„μž„ν•œλ‹€`() {
+fun `spot batch μ‘°νšŒλŠ” 쀑볡 idλ₯Ό μ œκ±°ν•œ λ’€ ν•œ 번의 finder 호좜둜 μœ„μž„ν•œλ‹€`() = runTest {
     val bloomingAppender = mockk<BloomingAppender>()
     val bloomingValidator = mockk<BloomingValidator>()
     val bloomingFinder = mockk<BloomingFinder>()
     val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder)
     // ... blooming setup ...

-    every { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) } returns bloomings
+    coEvery { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) } returns bloomings

     val result = service.recentlyBloomingBySpotIds(listOf(30L, 31L, 30L))

     result shouldBe bloomings
-    verify(exactly = 1) { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) }
+    coVerify(exactly = 1) { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) }
 }

The same fix applies to the event batch test (lines 91-96) and cafe batch test (lines 118-123).

πŸ“ 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
every { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) } returns bloomings
val result = service.recentlyBloomingBySpotIds(listOf(30L, 31L, 30L))
result shouldBe bloomings
verify(exactly = 1) { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) }
coEvery { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) } returns bloomings
val result = service.recentlyBloomingBySpotIds(listOf(30L, 31L, 30L))
result shouldBe bloomings
coVerify(exactly = 1) { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) }
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt`
around lines 64 - 69, The test calls the now-suspend function
recentlyBloomingBySpotIds using every/verify and runs outside a coroutine;
change the mocks to use coEvery { bloomingFinder.recentlyBloomingBySpotIds(...)
} returns ... and use coVerify { ... } for verification, and wrap the test body
in a coroutine test scope (e.g., kotlinx.coroutines.test.runTest { ... }) so
service.recentlyBloomingBySpotIds(...) is invoked from a coroutine; make the
same changes for the other two tests referencing the event batch and cafe batch
suspend calls.

}

@Test
fun `event batch μ‘°νšŒλŠ” 쀑볡 idλ₯Ό μ œκ±°ν•œ λ’€ ν•œ 번의 finder 호좜둜 μœ„μž„ν•œλ‹€`() {
val bloomingAppender = mockk<BloomingAppender>()
val bloomingValidator = mockk<BloomingValidator>()
val bloomingFinder = mockk<BloomingFinder>()
val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder)
val bloomings =
listOf(
Blooming(
id = 2L,
status = BloomingStatus.LITTLE,
userId = 11L,
flowerSpotId = null,
flowerEventId = 40L,
flowerSpotCafeId = null,
createdAt = LocalDateTime.of(2026, 4, 1, 10, 0),
),
)

every { bloomingFinder.recentlyBloomingByEventIds(listOf(40L, 41L)) } returns bloomings

val result = service.recentlyBloomingByEventIds(listOf(40L, 41L, 40L))

result shouldBe bloomings
verify(exactly = 1) { bloomingFinder.recentlyBloomingByEventIds(listOf(40L, 41L)) }
}

@Test
fun `cafe batch μ‘°νšŒλŠ” 쀑볡 idλ₯Ό μ œκ±°ν•œ λ’€ ν•œ 번의 finder 호좜둜 μœ„μž„ν•œλ‹€`() {
val bloomingAppender = mockk<BloomingAppender>()
val bloomingValidator = mockk<BloomingValidator>()
val bloomingFinder = mockk<BloomingFinder>()
val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder)
val bloomings =
listOf(
Blooming(
id = 3L,
status = BloomingStatus.WITHERED,
userId = 12L,
flowerSpotId = null,
flowerEventId = null,
flowerSpotCafeId = 50L,
createdAt = LocalDateTime.of(2026, 4, 1, 11, 0),
),
)

every { bloomingFinder.recentlyBloomingByCafeIds(listOf(50L, 51L)) } returns bloomings

val result = service.recentlyBloomingByCafeIds(listOf(50L, 51L, 50L))

result shouldBe bloomings
verify(exactly = 1) { bloomingFinder.recentlyBloomingByCafeIds(listOf(50L, 51L)) }
}

@Test
fun `flowerSpotId와 flowerEventIdκ°€ λͺ¨λ‘ μ—†μœΌλ©΄ INVALID_REQUESTλ₯Ό λ˜μ§„λ‹€`() {
val bloomingAppender = mockk<BloomingAppender>()
val bloomingValidator = mockk<BloomingValidator>()
val bloomingFinder = mockk<BloomingFinder>()
val cacheRepository = mockk<CacheRepository>(relaxed = true)
val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder, cacheRepository)
val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder)

val exception =
assertThrows<ErrorException> {
Expand All @@ -67,8 +145,7 @@ class BloomingServiceTest {
val bloomingAppender = mockk<BloomingAppender>()
val bloomingValidator = mockk<BloomingValidator>()
val bloomingFinder = mockk<BloomingFinder>()
val cacheRepository = mockk<CacheRepository>(relaxed = true)
val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder, cacheRepository)
val service = BloomingService(bloomingAppender, bloomingValidator, bloomingFinder)

val exception =
assertThrows<ErrorException> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,18 @@ class BloomingCoreRepository(
bloomingCustomRepository.recentlyByCafeId(cafeId).map { it.toBlooming() }
}

override fun findRecentBySpotIds(spotIds: List<Long>): List<Blooming> =
Tx.readable {
override suspend fun findRecentBySpotIds(spotIds: List<Long>): List<Blooming> =
Tx.coReadable {
bloomingCustomRepository.recentlyBySpotIds(spotIds).map { it.toBlooming() }
}

override fun findRecentByEventIds(eventIds: List<Long>): List<Blooming> =
Tx.readable {
override suspend fun findRecentByEventIds(eventIds: List<Long>): List<Blooming> =
Tx.coReadable {
bloomingCustomRepository.recentlyByEventIds(eventIds).map { it.toBlooming() }
}

override fun findRecentByCafeIds(cafeIds: List<Long>): List<Blooming> =
Tx.readable {
override suspend fun findRecentByCafeIds(cafeIds: List<Long>): List<Blooming> =
Tx.coReadable {
bloomingCustomRepository.recentlyByCafeIds(cafeIds).map { it.toBlooming() }
}

Expand Down
Loading