fix(recommend-live): 차단 관계를 추천 조회에 반영하고 캐시를 무효화한다

This commit is contained in:
2026-02-26 03:33:09 +09:00
parent e7252574d2
commit dd9cd788ca
8 changed files with 368 additions and 13 deletions

View File

@@ -0,0 +1,104 @@
package kr.co.vividnext.sodalive.live.recommend
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.configs.QueryDslConfig
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.block.BlockMember
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.Import
import java.time.LocalDateTime
import javax.persistence.EntityManager
@DataJpaTest(properties = ["spring.cache.type=none"])
@Import(QueryDslConfig::class)
class LiveRecommendRepositoryTest @Autowired constructor(
private val queryFactory: JPAQueryFactory,
private val memberRepository: MemberRepository,
private val blockMemberRepository: BlockMemberRepository,
private val recommendLiveCreatorBannerRepository: RecommendLiveCreatorBannerRepository,
private val entityManager: EntityManager
) {
private lateinit var liveRecommendRepository: LiveRecommendRepository
@BeforeEach
fun setup() {
liveRecommendRepository = LiveRecommendRepository(queryFactory, "https://cdn.test")
}
@Test
fun shouldExcludeBlockedCreatorsInBothDirections() {
val viewer = saveMember(nickname = "viewer", role = MemberRole.USER)
val creatorBlockedByViewer = saveMember(nickname = "creator-blocked-by-viewer", role = MemberRole.CREATOR)
val creatorBlockingViewer = saveMember(nickname = "creator-blocking-viewer", role = MemberRole.CREATOR)
val creatorAllowed = saveMember(nickname = "creator-allowed", role = MemberRole.CREATOR)
saveBanner(creator = creatorBlockedByViewer, order = 1)
saveBanner(creator = creatorBlockingViewer, order = 2)
saveBanner(creator = creatorAllowed, order = 3)
saveBlock(member = viewer, blockedMember = creatorBlockedByViewer, isActive = true)
saveBlock(member = creatorBlockingViewer, blockedMember = viewer, isActive = true)
entityManager.flush()
entityManager.clear()
val result = liveRecommendRepository.getRecommendLive(memberId = viewer.id, isAdult = true)
assertEquals(1, result.size)
assertEquals(creatorAllowed.id, result[0].creatorId)
}
@Test
fun shouldKeepCreatorWhenBlockRelationIsInactive() {
val viewer = saveMember(nickname = "viewer-inactive", role = MemberRole.USER)
val creator = saveMember(nickname = "creator-inactive", role = MemberRole.CREATOR)
saveBanner(creator = creator, order = 1)
saveBlock(member = viewer, blockedMember = creator, isActive = false)
entityManager.flush()
entityManager.clear()
val result = liveRecommendRepository.getRecommendLive(memberId = viewer.id, isAdult = true)
assertEquals(1, result.size)
assertEquals(creator.id, result[0].creatorId)
}
private fun saveMember(nickname: String, role: MemberRole): Member {
return memberRepository.saveAndFlush(
Member(
email = "$nickname@test.com",
password = "password",
nickname = nickname,
role = role
)
)
}
private fun saveBanner(creator: Member, order: Int) {
val banner = RecommendLiveCreatorBanner(
startDate = LocalDateTime.now().minusDays(1),
endDate = LocalDateTime.now().plusDays(1),
isAdult = false,
orders = order,
image = "recommend/$order.png"
)
banner.creator = creator
recommendLiveCreatorBannerRepository.saveAndFlush(banner)
}
private fun saveBlock(member: Member, blockedMember: Member, isActive: Boolean) {
val block = BlockMember(isActive = isActive)
block.member = member
block.blockedMember = blockedMember
blockMemberRepository.saveAndFlush(block)
}
}

View File

@@ -0,0 +1,62 @@
package kr.co.vividnext.sodalive.live.recommend
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.auth.Auth
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito
class LiveRecommendServiceTest {
private lateinit var repository: LiveRecommendRepository
private lateinit var blockMemberRepository: BlockMemberRepository
private lateinit var service: LiveRecommendService
@BeforeEach
fun setup() {
repository = Mockito.mock(LiveRecommendRepository::class.java)
blockMemberRepository = Mockito.mock(BlockMemberRepository::class.java)
service = LiveRecommendService(repository, blockMemberRepository)
}
@Test
fun shouldDelegateToRepositoryWithAdultFlagWhenMemberIsAuthenticated() {
val member = Member(
email = "member@test.com",
password = "password",
nickname = "member"
)
member.id = 10L
val auth = Auth(
name = "name",
birth = "19900101",
uniqueCi = "ci",
di = "di",
gender = 1
)
auth.member = member
val expected = listOf(GetRecommendLiveResponse(imageUrl = "https://cdn.test/recommend.png", creatorId = 77L))
Mockito.`when`(repository.getRecommendLive(memberId = member.id, isAdult = true)).thenReturn(expected)
val result = service.getRecommendLive(member)
assertEquals(expected, result)
Mockito.verify(repository).getRecommendLive(memberId = member.id, isAdult = true)
Mockito.verifyNoInteractions(blockMemberRepository)
}
@Test
fun shouldDelegateToRepositoryAsGuestWhenMemberIsNull() {
val expected = listOf(GetRecommendLiveResponse(imageUrl = "https://cdn.test/recommend-guest.png", creatorId = 88L))
Mockito.`when`(repository.getRecommendLive(memberId = null, isAdult = false)).thenReturn(expected)
val result = service.getRecommendLive(null)
assertEquals(expected, result)
Mockito.verify(repository).getRecommendLive(memberId = null, isAdult = false)
Mockito.verifyNoInteractions(blockMemberRepository)
}
}

View File

@@ -0,0 +1,128 @@
package kr.co.vividnext.sodalive.member
import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.vividnext.sodalive.common.CountryContext
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.auth.AuthRepository
import kr.co.vividnext.sodalive.member.block.BlockMember
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import kr.co.vividnext.sodalive.member.block.MemberBlockRequest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.springframework.cache.Cache
import org.springframework.cache.CacheManager
import java.util.Optional
class MemberServiceCacheEvictionTest {
private lateinit var memberRepository: MemberRepository
private lateinit var blockMemberRepository: BlockMemberRepository
private lateinit var authRepository: AuthRepository
private lateinit var cacheManager: CacheManager
private lateinit var cache: Cache
private lateinit var service: MemberService
@BeforeEach
fun setup() {
memberRepository = mock()
blockMemberRepository = mock()
authRepository = mock()
cacheManager = mock()
cache = mock()
Mockito.`when`(cacheManager.getCache("cache_ttl_3_hours")).thenReturn(cache)
service = MemberService(
repository = memberRepository,
tokenRepository = mock(),
stipulationRepository = mock(),
stipulationAgreeRepository = mock(),
creatorFollowingRepository = mock(),
blockMemberRepository = blockMemberRepository,
authRepository = authRepository,
signOutRepository = mock(),
nicknameChangeLogRepository = mock(),
memberTagRepository = mock(),
liveReservationRepository = mock(),
chargeRepository = mock(),
memberPointRepository = mock(),
orderService = mock(),
emailService = mock(),
pushTokenService = mock(),
canPaymentService = mock(),
nicknameGenerateService = mock(),
memberNotificationService = mock(),
s3Uploader = mock(),
validator = mock(),
tokenProvider = mock(),
passwordEncoder = mock(),
authenticationManagerBuilder = mock(),
messageSource = SodaMessageSource(),
langContext = LangContext(),
countryContext = CountryContext(),
objectMapper = ObjectMapper(),
cacheManager = cacheManager,
s3Bucket = "test-bucket",
cloudFrontHost = "https://cdn.test"
)
}
@Test
fun shouldEvictRecommendLiveCacheForRequesterAndTargetOnBlock() {
val memberId = 100L
val blockedMemberId = 200L
val member = createMember(id = memberId, nickname = "requester")
val blockedMember = createMember(id = blockedMemberId, nickname = "target")
Mockito.`when`(memberRepository.findById(memberId)).thenReturn(Optional.of(member))
Mockito.`when`(memberRepository.findById(blockedMemberId)).thenReturn(Optional.of(blockedMember))
Mockito.`when`(
blockMemberRepository.getBlockAccount(
blockedMemberId = blockedMemberId,
memberId = memberId
)
).thenReturn(null)
service.memberBlock(MemberBlockRequest(blockMemberId = blockedMemberId), memberId)
Mockito.verify(cache).evict("getRecommendLive:$memberId")
Mockito.verify(cache).evict("getRecommendLive:$blockedMemberId")
Mockito.verifyNoInteractions(authRepository)
}
@Test
fun shouldEvictRecommendLiveCacheForRequesterAndTargetOnUnblock() {
val memberId = 300L
val blockedMemberId = 400L
val blockMember = BlockMember(isActive = true)
Mockito.`when`(
blockMemberRepository.getBlockAccount(
blockedMemberId = blockedMemberId,
memberId = memberId
)
).thenReturn(blockMember)
service.memberUnBlock(MemberBlockRequest(blockMemberId = blockedMemberId), memberId)
assertEquals(false, blockMember.isActive)
Mockito.verify(cache).evict("getRecommendLive:$memberId")
Mockito.verify(cache).evict("getRecommendLive:$blockedMemberId")
}
private fun createMember(id: Long, nickname: String): Member {
val member = Member(
email = "$nickname@test.com",
password = "password",
nickname = nickname
)
member.id = id
return member
}
private inline fun <reified T> mock(): T {
return Mockito.mock(T::class.java)
}
}