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)) }
}

@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)) }
}
Comment on lines +45 to +124
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 | πŸ”΄ Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify coroutine-test usage and MockK coroutine DSL for new batch tests.
target="pida-core/core-domain/src/test/kotlin/com/pida/blooming/BloomingServiceTest.kt"

echo "[1] New batch tests declared without runTest:"
rg -nP 'fun `.*batch 쑰회.*`\(\)\s*\{' "$target"

echo "[2] Suspend finder stubs using every (should be coEvery):"
rg -nP 'every \{ bloomingFinder\.recentlyBloomingBy(Spot|Event|Cafe)Ids' "$target"

echo "[3] Suspend finder verifies using verify (should be coVerify):"
rg -nP 'verify\(exactly\s*=\s*1\)\s*\{ bloomingFinder\.recentlyBloomingBy(Spot|Event|Cafe)Ids' "$target"

echo "[4] Presence of coroutine-test constructs:"
rg -nP 'runTest|coEvery|coVerify' "$target"

Repository: Team-PIDA/Pida-Server

Length of output: 1054


Suspend test calls need coroutine test scope and MockK coroutine DSL.

The three batch test functions (lines 46, 73, 100) invoke suspend service methods without wrapping in runTest. Additionally, suspend finder methods are stubbed and verified with every/verify instead of coEvery/coVerify.

πŸ”§ Proposed fix pattern (apply to all three batch tests)
+import io.mockk.coEvery
+import io.mockk.coVerify
+import kotlinx.coroutines.test.runTest
@@
-    fun `spot batch μ‘°νšŒλŠ” 쀑볡 idλ₯Ό μ œκ±°ν•œ λ’€ ν•œ 번의 finder 호좜둜 μœ„μž„ν•œλ‹€`() {
+    fun `spot batch μ‘°νšŒλŠ” 쀑볡 idλ₯Ό μ œκ±°ν•œ λ’€ ν•œ 번의 finder 호좜둜 μœ„μž„ν•œλ‹€`() = runTest {
@@
-        every { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) } returns bloomings
+        coEvery { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) } returns bloomings
@@
-        verify(exactly = 1) { bloomingFinder.recentlyBloomingBySpotIds(listOf(30L, 31L)) }
+        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 45 - 124, Tests calling suspend methods recentlyBloomingBySpotIds,
recentlyBloomingByEventIds, and recentlyBloomingByCafeIds must run in a
coroutine test scope and use MockK's coroutine stubs/verifications: wrap each
test body in kotlinx.coroutines.test.runTest and replace every/verify with
coEvery/coVerify when stubbing/verifying
bloomingFinder.recentlyBloomingBySpotIds,
bloomingFinder.recentlyBloomingByEventIds, and
bloomingFinder.recentlyBloomingByCafeIds respectively so the suspend
interactions are executed and asserted correctly.


@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