fix(recommend-live): 차단 관계를 추천 조회에 반영하고 캐시를 무효화한다 #391
17
docs/20260226_라이브추천차단조인및캐시무효화.md
Normal file
17
docs/20260226_라이브추천차단조인및캐시무효화.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# 라이브 추천 차단 JOIN 및 캐시 무효화
|
||||
|
||||
- [x] `LiveRecommendService.getRecommendLive`의 차단 필터 처리 구조 점검
|
||||
- [x] `LiveRecommendRepository.getRecommendLive`를 DB 조회 시 차단 관계를 JOIN/조건으로 제외하도록 변경
|
||||
- [x] 차단(`memberBlock`) 및 차단 해제(`memberUnBlock`) 시 추천 라이브 캐시가 즉시 반영되도록 무효화 처리
|
||||
- [x] 변경 코드 정적 진단 및 테스트/빌드 검증
|
||||
- [x] 검증 기록 작성
|
||||
|
||||
## 검증 기록
|
||||
|
||||
### 1차 구현
|
||||
- 무엇을: `getRecommendLive`의 차단 제외 로직을 서비스 단 필터링에서 QueryDSL `leftJoin(blockMember)` + `blockMember.id.isNull` 조건으로 이동했고, 차단/차단해제 시 `CacheManager`로 `getRecommendLive:{memberId}` 키를 직접 evict 하도록 적용했다.
|
||||
- 왜: 기존 방식은 추천 결과 조회 후 creator마다 `isBlocked`를 반복 호출해 후처리하고, 캐시 만료 전까지 차단/해제 결과가 반영되지 않는 문제가 있어 DB 레벨 필터링과 이벤트성 캐시 무효화가 필요했다.
|
||||
- 어떻게:
|
||||
- `lsp_diagnostics` (대상: `LiveRecommendRepository.kt`, `LiveRecommendService.kt`, `MemberService.kt`) 실행 결과: **환경상 Kotlin LSP 미구성으로 진단 불가**
|
||||
- `./gradlew test` 실행 결과: **성공 (BUILD SUCCESSFUL)**
|
||||
- `./gradlew build` 실행 결과: **성공 (BUILD SUCCESSFUL, ktlint/check 포함)**
|
||||
21
docs/20260226_라이브추천차단조인캐시무효화검증테스트.md
Normal file
21
docs/20260226_라이브추천차단조인캐시무효화검증테스트.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# 라이브 추천 차단 JOIN/캐시 무효화 검증 테스트
|
||||
|
||||
- [x] `LiveRecommendRepository.getRecommendLive`가 차단 관계(`member -> creator`, `creator -> member`)를 DB 조회 단계에서 제외하는지 테스트 추가
|
||||
- [x] `LiveRecommendService.getRecommendLive`가 서비스 단 후처리 없이 저장소 결과를 그대로 위임하는지 테스트 추가
|
||||
- [x] `MemberService.memberBlock`/`memberUnBlock` 호출 시 추천 라이브 캐시 키(`getRecommendLive:{memberId}`)가 즉시 무효화되는지 테스트 추가
|
||||
- [x] 테스트 및 빌드 검증 수행
|
||||
- [x] 검증 기록 작성
|
||||
|
||||
## 검증 기록
|
||||
|
||||
### 1차 검증 테스트 구현
|
||||
- 무엇을: 문서 요구사항(추천 라이브 차단 JOIN, 서비스 위임 구조, 차단/해제 시 캐시 무효화)을 검증하는 테스트 3종을 추가했다.
|
||||
- `src/test/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepositoryTest.kt`
|
||||
- `src/test/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendServiceTest.kt`
|
||||
- `src/test/kotlin/kr/co/vividnext/sodalive/member/MemberServiceCacheEvictionTest.kt`
|
||||
- 왜: `docs/20260226_라이브추천차단조인및캐시무효화.md`에 기재된 구현이 실제 코드에서 회귀 없이 유지되는지 자동 검증이 필요하다.
|
||||
- 어떻게:
|
||||
- `lsp_diagnostics` (대상: 위 3개 Kotlin 테스트 파일) 실행 결과: **환경상 Kotlin LSP 미구성으로 진단 불가**
|
||||
- `./gradlew test --tests "kr.co.vividnext.sodalive.live.recommend.LiveRecommendRepositoryTest" --tests "kr.co.vividnext.sodalive.live.recommend.LiveRecommendServiceTest" --tests "kr.co.vividnext.sodalive.member.MemberServiceCacheEvictionTest"` 실행 결과: **성공 (BUILD SUCCESSFUL)**
|
||||
- `./gradlew build` 1차 실행 결과: **실패 (`MemberServiceCacheEvictionTest.kt` 라인 길이/인자 줄바꿈 ktlint 위반)**
|
||||
- `MemberServiceCacheEvictionTest.kt` 포맷 수정 후 `./gradlew build` 재실행 결과: **성공 (BUILD SUCCESSFUL, test/check/ktlint 통과)**
|
||||
@@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.live.recommend.QRecommendLiveCreatorBanner.recom
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
|
||||
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Repository
|
||||
@@ -20,7 +21,7 @@ class LiveRecommendRepository(
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
fun getRecommendLive(
|
||||
isBlocked: (Long) -> Boolean,
|
||||
memberId: Long?,
|
||||
isAdult: Boolean
|
||||
): List<GetRecommendLiveResponse> {
|
||||
val dateNow = LocalDateTime.now()
|
||||
@@ -32,7 +33,7 @@ class LiveRecommendRepository(
|
||||
where = where.and(recommendLiveCreatorBanner.isAdult.isFalse)
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
var select = queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
GetRecommendLiveResponse::class.java,
|
||||
@@ -41,12 +42,26 @@ class LiveRecommendRepository(
|
||||
)
|
||||
)
|
||||
.from(recommendLiveCreatorBanner)
|
||||
|
||||
if (memberId != null) {
|
||||
val blockMemberCondition = blockMember.isActive.isTrue
|
||||
.and(
|
||||
blockMember.member.id.eq(recommendLiveCreatorBanner.creator.id)
|
||||
.and(blockMember.blockedMember.id.eq(memberId))
|
||||
.or(
|
||||
blockMember.member.id.eq(memberId)
|
||||
.and(blockMember.blockedMember.id.eq(recommendLiveCreatorBanner.creator.id))
|
||||
)
|
||||
)
|
||||
|
||||
where = where.and(blockMember.id.isNull)
|
||||
select = select.leftJoin(blockMember).on(blockMemberCondition)
|
||||
}
|
||||
|
||||
return select
|
||||
.where(where)
|
||||
.orderBy(recommendLiveCreatorBanner.orders.asc())
|
||||
.fetch()
|
||||
.asSequence()
|
||||
.filter { !isBlocked(it.creatorId) }
|
||||
.toList()
|
||||
}
|
||||
|
||||
fun getOnAirRecommendChannelList(
|
||||
|
||||
@@ -16,17 +16,11 @@ class LiveRecommendService(
|
||||
@Transactional(readOnly = true)
|
||||
@Cacheable(
|
||||
cacheNames = ["cache_ttl_3_hours"],
|
||||
key = "'getRecommendLive:' + (#member ?: 'guest')"
|
||||
key = "'getRecommendLive:' + (#member?.id ?: 'guest')"
|
||||
)
|
||||
fun getRecommendLive(member: Member?): List<GetRecommendLiveResponse> {
|
||||
return repository.getRecommendLive(
|
||||
isBlocked = {
|
||||
if (member != null) {
|
||||
isBlockedBetweenMembers(memberId = member.id!!, creatorId = it)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
memberId = member?.id,
|
||||
isAdult = member?.auth != null
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ import kr.co.vividnext.sodalive.point.MemberPointRepository
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
import kr.co.vividnext.sodalive.utils.generatePassword
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.cache.CacheManager
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||
@@ -107,6 +108,7 @@ class MemberService(
|
||||
private val countryContext: CountryContext,
|
||||
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val cacheManager: CacheManager,
|
||||
|
||||
@Value("\${cloud.aws.s3.bucket}")
|
||||
private val s3Bucket: String,
|
||||
@@ -117,6 +119,8 @@ class MemberService(
|
||||
|
||||
private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf()
|
||||
|
||||
private val recommendLiveCacheKeyPrefix = "getRecommendLive:"
|
||||
|
||||
@Transactional
|
||||
fun signUpV2(request: SignUpRequestV2): SignUpResponse {
|
||||
val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID)
|
||||
@@ -558,6 +562,9 @@ class MemberService(
|
||||
blockMember.isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
evictRecommendLiveCache(memberId)
|
||||
blockTargetMemberIds.forEach { evictRecommendLiveCache(it) }
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -570,6 +577,9 @@ class MemberService(
|
||||
if (blockMember != null) {
|
||||
blockMember.isActive = false
|
||||
}
|
||||
|
||||
evictRecommendLiveCache(memberId)
|
||||
evictRecommendLiveCache(request.blockMemberId)
|
||||
}
|
||||
|
||||
fun isBlocked(blockedMemberId: Long, memberId: Long) = blockMemberRepository.isBlocked(blockedMemberId, memberId)
|
||||
@@ -829,6 +839,10 @@ class MemberService(
|
||||
return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() }
|
||||
}
|
||||
|
||||
private fun evictRecommendLiveCache(memberId: Long) {
|
||||
cacheManager.getCache("cache_ttl_3_hours")?.evict(recommendLiveCacheKeyPrefix + memberId)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun updateMarketingInfo(memberId: Long, adid: String, pid: String): String? {
|
||||
val member = repository.findByIdOrNull(id = memberId)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user