feat(content-ranking): 랭킹 조회 fallback과 차단 필터를 적용한다
This commit is contained in:
@@ -5,10 +5,11 @@ import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreference
|
|||||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRanking
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRanking
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingItem
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingItem
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingBlockPort
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
@@ -16,24 +17,27 @@ import java.time.ZonedDateTime
|
|||||||
class AudioRankingQueryService(
|
class AudioRankingQueryService(
|
||||||
private val snapshotPort: AudioRankingSnapshotPort,
|
private val snapshotPort: AudioRankingSnapshotPort,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
|
private val blockPort: AudioRankingBlockPort,
|
||||||
|
private val jobService: AudioRankingSnapshotJobService,
|
||||||
private val nowProvider: () -> ZonedDateTime = { ZonedDateTime.now() }
|
private val nowProvider: () -> ZonedDateTime = { ZonedDateTime.now() }
|
||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
fun getRankings(type: AudioRankingType, member: Member?): AudioRanking {
|
fun getRankings(type: AudioRankingType, member: Member?): AudioRanking {
|
||||||
val nowUtc = nowProvider().withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
|
val nowUtc = nowProvider().withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
|
||||||
val latestSnapshots = snapshotPort.findLatestVisibleSnapshots(type, nowUtc)
|
val latestSnapshots = findLatestVisibleSnapshots(type, nowUtc)
|
||||||
if (latestSnapshots.isEmpty()) {
|
if (latestSnapshots.isEmpty()) {
|
||||||
return AudioRanking(showRankChange = false, type = type, items = emptyList())
|
return AudioRanking(showRankChange = false, type = type, items = emptyList())
|
||||||
}
|
}
|
||||||
val canViewAdultContent = canViewAdultContent(member)
|
val canViewAdultContent = canViewAdultContent(member)
|
||||||
val latestVisibleSnapshots = latestSnapshots.visibleTo(canViewAdultContent).take(ITEM_LIMIT)
|
|
||||||
|
|
||||||
val previousSnapshots = snapshotPort.findPreviousVisibleSnapshots(
|
val previousSnapshots = snapshotPort.findPreviousVisibleSnapshots(
|
||||||
rankingType = type,
|
rankingType = type,
|
||||||
currentAggregationStartAtUtc = latestSnapshots.first().aggregationStartAtUtc,
|
currentAggregationStartAtUtc = latestSnapshots.first().aggregationStartAtUtc,
|
||||||
nowUtc = nowUtc
|
nowUtc = nowUtc
|
||||||
)
|
)
|
||||||
val previousRankByContentId = previousSnapshots.visibleTo(canViewAdultContent)
|
val blockedCreatorMemberIds = blockedCreatorMemberIds(member, latestSnapshots + previousSnapshots)
|
||||||
|
val latestVisibleSnapshots = latestSnapshots.visibleTo(canViewAdultContent, blockedCreatorMemberIds).take(ITEM_LIMIT)
|
||||||
|
val previousRankByContentId = previousSnapshots.visibleTo(canViewAdultContent, blockedCreatorMemberIds)
|
||||||
.take(ITEM_LIMIT)
|
.take(ITEM_LIMIT)
|
||||||
.mapIndexed { index, snapshot -> snapshot.contentId to index + 1 }
|
.mapIndexed { index, snapshot -> snapshot.contentId to index + 1 }
|
||||||
.toMap()
|
.toMap()
|
||||||
@@ -48,13 +52,44 @@ class AudioRankingQueryService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findLatestVisibleSnapshots(
|
||||||
|
type: AudioRankingType,
|
||||||
|
nowUtc: java.time.LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotRecord> {
|
||||||
|
val latestSnapshots = snapshotPort.findLatestVisibleSnapshots(type, nowUtc)
|
||||||
|
if (latestSnapshots.isNotEmpty()) return latestSnapshots
|
||||||
|
|
||||||
|
runCatching { jobService.refreshLastCompletedWeekByFallback(type) }
|
||||||
|
.onFailure { ex ->
|
||||||
|
log.warn(
|
||||||
|
"event=audio_ranking_query_fallback_failure rankingType={} error={}",
|
||||||
|
type,
|
||||||
|
ex.message,
|
||||||
|
ex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return snapshotPort.findLatestVisibleSnapshots(type, nowUtc)
|
||||||
|
}
|
||||||
|
|
||||||
private fun canViewAdultContent(member: Member?): Boolean {
|
private fun canViewAdultContent(member: Member?): Boolean {
|
||||||
if (member == null) return false
|
if (member == null) return false
|
||||||
return memberContentPreferenceService.canViewAdultContent(member)
|
return memberContentPreferenceService.canViewAdultContent(member)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<AudioRankingSnapshotRecord>.visibleTo(canViewAdultContent: Boolean): List<AudioRankingSnapshotRecord> {
|
private fun blockedCreatorMemberIds(member: Member?, snapshots: List<AudioRankingSnapshotRecord>): Set<Long> {
|
||||||
return if (canViewAdultContent) this else filter { !it.isAdult }
|
val memberId = member?.id ?: return emptySet()
|
||||||
|
val creatorMemberIds = snapshots.map { it.creatorMemberId }.toSet()
|
||||||
|
if (creatorMemberIds.isEmpty()) return emptySet()
|
||||||
|
return blockPort.findBlockedCreatorMemberIds(memberId, creatorMemberIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<AudioRankingSnapshotRecord>.visibleTo(
|
||||||
|
canViewAdultContent: Boolean,
|
||||||
|
blockedCreatorMemberIds: Set<Long>
|
||||||
|
): List<AudioRankingSnapshotRecord> {
|
||||||
|
return filter { snapshot ->
|
||||||
|
(canViewAdultContent || !snapshot.isAdult) && snapshot.creatorMemberId !in blockedCreatorMemberIds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AudioRankingSnapshotRecord.toItem(
|
private fun AudioRankingSnapshotRecord.toItem(
|
||||||
|
|||||||
@@ -3,18 +3,35 @@ package kr.co.vividnext.sodalive.v2.content.ranking.application
|
|||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingBlockPort
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.boot.test.system.CapturedOutput
|
||||||
|
import org.springframework.boot.test.system.OutputCaptureExtension
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
@ExtendWith(OutputCaptureExtension::class)
|
||||||
class AudioRankingQueryServiceTest {
|
class AudioRankingQueryServiceTest {
|
||||||
|
@Test
|
||||||
|
fun shouldNotWrapGetRankingsInTransactionSoFallbackRequeryUsesFreshSnapshot() {
|
||||||
|
val method = AudioRankingQueryService::class.java.getDeclaredMethod(
|
||||||
|
"getRankings",
|
||||||
|
AudioRankingType::class.java,
|
||||||
|
Member::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(null, method.getAnnotation(Transactional::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldReturnLatestVisibleSnapshotsWithRankChangesAndNewFlags() {
|
fun shouldReturnLatestVisibleSnapshotsWithRankChangesAndNewFlags() {
|
||||||
val snapshotPort = FakeAudioRankingQuerySnapshotPort()
|
val snapshotPort = FakeAudioRankingQuerySnapshotPort()
|
||||||
@@ -96,9 +113,113 @@ class AudioRankingQueryServiceTest {
|
|||||||
assertEquals(listOf(1L), result.items.map { it.contentId })
|
assertEquals(listOf(1L), result.items.map { it.contentId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldFilterBlockedCreatorSnapshotsForMemberAndRecalculateRanks() {
|
||||||
|
val snapshotPort = FakeAudioRankingQuerySnapshotPort()
|
||||||
|
val blockPort = FakeAudioRankingBlockPort(blockedCreatorMemberIds = setOf(102L))
|
||||||
|
val member = member(id = 7L)
|
||||||
|
snapshotPort.latestSnapshots = listOf(
|
||||||
|
snapshot(contentId = 1L, rank = 1, creatorMemberId = 101L),
|
||||||
|
snapshot(contentId = 2L, rank = 2, creatorMemberId = 102L),
|
||||||
|
snapshot(contentId = 3L, rank = 3, creatorMemberId = 103L)
|
||||||
|
)
|
||||||
|
snapshotPort.previousSnapshots = listOf(
|
||||||
|
snapshot(contentId = 2L, rank = 1, creatorMemberId = 102L),
|
||||||
|
snapshot(contentId = 1L, rank = 2, creatorMemberId = 101L)
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = service(snapshotPort, blockPort = blockPort).getRankings(AudioRankingType.REVENUE, member)
|
||||||
|
|
||||||
|
assertEquals(listOf(1L, 3L), result.items.map { it.contentId })
|
||||||
|
assertEquals(listOf(1, 2), result.items.map { it.rank })
|
||||||
|
assertEquals(listOf(0, null), result.items.map { it.rankChange })
|
||||||
|
assertEquals(7L, blockPort.memberId)
|
||||||
|
assertEquals(setOf(101L, 102L, 103L), blockPort.creatorMemberIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldNotLookupBlockedCreatorsForAnonymousViewer() {
|
||||||
|
val snapshotPort = FakeAudioRankingQuerySnapshotPort()
|
||||||
|
val blockPort = FakeAudioRankingBlockPort(blockedCreatorMemberIds = setOf(101L))
|
||||||
|
snapshotPort.latestSnapshots = listOf(snapshot(contentId = 1L, rank = 1, creatorMemberId = 101L))
|
||||||
|
|
||||||
|
val result = service(snapshotPort, blockPort = blockPort).getRankings(AudioRankingType.REVENUE, member = null)
|
||||||
|
|
||||||
|
assertEquals(listOf(1L), result.items.map { it.contentId })
|
||||||
|
assertEquals(0, blockPort.callCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldRunFallbackAndRequeryWhenLatestVisibleSnapshotDoesNotExist() {
|
||||||
|
val snapshotPort = FakeAudioRankingQuerySnapshotPort()
|
||||||
|
val jobService = Mockito.mock(AudioRankingSnapshotJobService::class.java)
|
||||||
|
snapshotPort.latestSnapshotsByCall = listOf(
|
||||||
|
emptyList(),
|
||||||
|
listOf(snapshot(contentId = 1L, rank = 1))
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = service(snapshotPort, jobService = jobService).getRankings(AudioRankingType.LIKE_COUNT, member = null)
|
||||||
|
|
||||||
|
assertEquals(listOf(1L), result.items.map { it.contentId })
|
||||||
|
assertEquals(2, snapshotPort.latestCallCount)
|
||||||
|
Mockito.verify(jobService).refreshLastCompletedWeekByFallback(AudioRankingType.LIKE_COUNT)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldReturnEmptyRankingWhenFallbackFails(output: CapturedOutput) {
|
||||||
|
val snapshotPort = FakeAudioRankingQuerySnapshotPort()
|
||||||
|
val jobService = Mockito.mock(AudioRankingSnapshotJobService::class.java)
|
||||||
|
Mockito.doThrow(IllegalStateException("aggregate failed"))
|
||||||
|
.`when`(jobService).refreshLastCompletedWeekByFallback(AudioRankingType.LIKE_COUNT)
|
||||||
|
|
||||||
|
val result = service(snapshotPort, jobService = jobService).getRankings(AudioRankingType.LIKE_COUNT, member = null)
|
||||||
|
|
||||||
|
assertFalse(result.showRankChange)
|
||||||
|
assertEquals(AudioRankingType.LIKE_COUNT, result.type)
|
||||||
|
assertEquals(emptyList<Any>(), result.items)
|
||||||
|
assertTrue(output.out.contains("event=audio_ranking_query_fallback_failure"))
|
||||||
|
assertTrue(output.out.contains("rankingType=LIKE_COUNT"))
|
||||||
|
assertTrue(output.out.contains("error=aggregate failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldNotRunFallbackWhenLatestVisibleSnapshotExists() {
|
||||||
|
val snapshotPort = FakeAudioRankingQuerySnapshotPort()
|
||||||
|
val jobService = Mockito.mock(AudioRankingSnapshotJobService::class.java)
|
||||||
|
snapshotPort.latestSnapshots = listOf(snapshot(contentId = 1L, rank = 1))
|
||||||
|
|
||||||
|
service(snapshotPort, jobService = jobService).getRankings(AudioRankingType.REVENUE, member = null)
|
||||||
|
|
||||||
|
Mockito.verify(jobService, Mockito.never()).refreshLastCompletedWeekByFallback(AudioRankingType.REVENUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldFilterPreviousOnlyBlockedCreatorWhenCalculatingRankChanges() {
|
||||||
|
val snapshotPort = FakeAudioRankingQuerySnapshotPort()
|
||||||
|
val blockPort = FakeAudioRankingBlockPort(blockedCreatorMemberIds = setOf(999L))
|
||||||
|
val member = member(id = 7L)
|
||||||
|
snapshotPort.latestSnapshots = listOf(
|
||||||
|
snapshot(contentId = 1L, rank = 1, creatorMemberId = 101L),
|
||||||
|
snapshot(contentId = 2L, rank = 2, creatorMemberId = 102L)
|
||||||
|
)
|
||||||
|
snapshotPort.previousSnapshots = listOf(
|
||||||
|
snapshot(contentId = 99L, rank = 1, creatorMemberId = 999L),
|
||||||
|
snapshot(contentId = 1L, rank = 2, creatorMemberId = 101L),
|
||||||
|
snapshot(contentId = 2L, rank = 3, creatorMemberId = 102L)
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = service(snapshotPort, blockPort = blockPort).getRankings(AudioRankingType.REVENUE, member)
|
||||||
|
|
||||||
|
assertEquals(setOf(101L, 102L, 999L), blockPort.creatorMemberIds)
|
||||||
|
assertEquals(listOf(0, 0), result.items.map { it.rankChange })
|
||||||
|
assertEquals(listOf(false, false), result.items.map { it.isNew })
|
||||||
|
}
|
||||||
|
|
||||||
private fun service(
|
private fun service(
|
||||||
snapshotPort: FakeAudioRankingQuerySnapshotPort,
|
snapshotPort: FakeAudioRankingQuerySnapshotPort,
|
||||||
adultMember: Member? = null
|
adultMember: Member? = null,
|
||||||
|
blockPort: AudioRankingBlockPort = FakeAudioRankingBlockPort(),
|
||||||
|
jobService: AudioRankingSnapshotJobService = Mockito.mock(AudioRankingSnapshotJobService::class.java)
|
||||||
): AudioRankingQueryService {
|
): AudioRankingQueryService {
|
||||||
val memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
val memberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
||||||
if (adultMember != null) {
|
if (adultMember != null) {
|
||||||
@@ -107,15 +228,22 @@ class AudioRankingQueryServiceTest {
|
|||||||
return AudioRankingQueryService(
|
return AudioRankingQueryService(
|
||||||
snapshotPort = snapshotPort,
|
snapshotPort = snapshotPort,
|
||||||
memberContentPreferenceService = memberContentPreferenceService,
|
memberContentPreferenceService = memberContentPreferenceService,
|
||||||
|
blockPort = blockPort,
|
||||||
|
jobService = jobService,
|
||||||
nowProvider = { ZonedDateTime.of(2026, 6, 8, 9, 0, 0, 0, ZoneId.of("Asia/Seoul")) }
|
nowProvider = { ZonedDateTime.of(2026, 6, 8, 9, 0, 0, 0, ZoneId.of("Asia/Seoul")) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun member(id: Long): Member {
|
||||||
|
return Member(password = "password", nickname = "member-$id").also { it.id = id }
|
||||||
|
}
|
||||||
|
|
||||||
private fun snapshot(
|
private fun snapshot(
|
||||||
contentId: Long,
|
contentId: Long,
|
||||||
rank: Int,
|
rank: Int,
|
||||||
rankingType: AudioRankingType = AudioRankingType.REVENUE,
|
rankingType: AudioRankingType = AudioRankingType.REVENUE,
|
||||||
isAdult: Boolean = false
|
isAdult: Boolean = false,
|
||||||
|
creatorMemberId: Long = 100L + contentId
|
||||||
): AudioRankingSnapshotRecord {
|
): AudioRankingSnapshotRecord {
|
||||||
return AudioRankingSnapshotRecord(
|
return AudioRankingSnapshotRecord(
|
||||||
rankingType = rankingType,
|
rankingType = rankingType,
|
||||||
@@ -124,7 +252,7 @@ class AudioRankingQueryServiceTest {
|
|||||||
visibleFromAtUtc = LocalDateTime.of(2026, 6, 8, 0, 0),
|
visibleFromAtUtc = LocalDateTime.of(2026, 6, 8, 0, 0),
|
||||||
contentId = contentId,
|
contentId = contentId,
|
||||||
title = "audio-$contentId",
|
title = "audio-$contentId",
|
||||||
creatorMemberId = 100L + contentId,
|
creatorMemberId = creatorMemberId,
|
||||||
creatorNickname = "creator-$contentId",
|
creatorNickname = "creator-$contentId",
|
||||||
coverImageUrl = "cover-$contentId.png",
|
coverImageUrl = "cover-$contentId.png",
|
||||||
releaseDate = LocalDateTime.of(2026, 6, 1, 0, 0),
|
releaseDate = LocalDateTime.of(2026, 6, 1, 0, 0),
|
||||||
@@ -137,15 +265,21 @@ class AudioRankingQueryServiceTest {
|
|||||||
|
|
||||||
private class FakeAudioRankingQuerySnapshotPort : AudioRankingSnapshotPort {
|
private class FakeAudioRankingQuerySnapshotPort : AudioRankingSnapshotPort {
|
||||||
var latestSnapshots: List<AudioRankingSnapshotRecord> = emptyList()
|
var latestSnapshots: List<AudioRankingSnapshotRecord> = emptyList()
|
||||||
|
var latestSnapshotsByCall: List<List<AudioRankingSnapshotRecord>> = emptyList()
|
||||||
var previousSnapshots: List<AudioRankingSnapshotRecord> = emptyList()
|
var previousSnapshots: List<AudioRankingSnapshotRecord> = emptyList()
|
||||||
var nowUtc: LocalDateTime? = null
|
var nowUtc: LocalDateTime? = null
|
||||||
var currentAggregationStartAtUtc: LocalDateTime? = null
|
var currentAggregationStartAtUtc: LocalDateTime? = null
|
||||||
|
var latestCallCount: Int = 0
|
||||||
|
|
||||||
override fun findLatestVisibleSnapshots(
|
override fun findLatestVisibleSnapshots(
|
||||||
rankingType: AudioRankingType,
|
rankingType: AudioRankingType,
|
||||||
nowUtc: LocalDateTime
|
nowUtc: LocalDateTime
|
||||||
): List<AudioRankingSnapshotRecord> {
|
): List<AudioRankingSnapshotRecord> {
|
||||||
this.nowUtc = nowUtc
|
this.nowUtc = nowUtc
|
||||||
|
latestCallCount += 1
|
||||||
|
if (latestSnapshotsByCall.isNotEmpty()) {
|
||||||
|
return latestSnapshotsByCall.getOrElse(latestCallCount - 1) { latestSnapshotsByCall.last() }
|
||||||
|
}
|
||||||
return latestSnapshots
|
return latestSnapshots
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,3 +300,18 @@ private class FakeAudioRankingQuerySnapshotPort : AudioRankingSnapshotPort {
|
|||||||
newSnapshots: List<AudioRankingSnapshotRecord>
|
newSnapshots: List<AudioRankingSnapshotRecord>
|
||||||
) = error("Query service test does not replace snapshots")
|
) = error("Query service test does not replace snapshots")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FakeAudioRankingBlockPort(
|
||||||
|
private val blockedCreatorMemberIds: Set<Long> = emptySet()
|
||||||
|
) : AudioRankingBlockPort {
|
||||||
|
var memberId: Long? = null
|
||||||
|
var creatorMemberIds: Set<Long> = emptySet()
|
||||||
|
var callCount: Int = 0
|
||||||
|
|
||||||
|
override fun findBlockedCreatorMemberIds(memberId: Long, creatorMemberIds: Set<Long>): Set<Long> {
|
||||||
|
callCount += 1
|
||||||
|
this.memberId = memberId
|
||||||
|
this.creatorMemberIds = creatorMemberIds
|
||||||
|
return blockedCreatorMemberIds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user