test #426
@@ -0,0 +1,59 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.content.ranking.adapter.out.scheduler
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.application.AudioRankingSnapshotJobService
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||||
|
import org.redisson.api.RedissonClient
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class AudioRankingSnapshotScheduler(
|
||||||
|
private val jobService: AudioRankingSnapshotJobService,
|
||||||
|
private val redissonClient: RedissonClient
|
||||||
|
) {
|
||||||
|
@Scheduled(cron = "0 0 2 * * MON", zone = "Asia/Seoul")
|
||||||
|
fun refreshWeeklyPopular() {
|
||||||
|
refresh(AudioRankingType.WEEKLY_POPULAR)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 3 * * MON", zone = "Asia/Seoul")
|
||||||
|
fun refreshRising() {
|
||||||
|
refresh(AudioRankingType.RISING)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 4 * * MON", zone = "Asia/Seoul")
|
||||||
|
fun refreshRevenue() {
|
||||||
|
refresh(AudioRankingType.REVENUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 5 * * MON", zone = "Asia/Seoul")
|
||||||
|
fun refreshSalesCount() {
|
||||||
|
refresh(AudioRankingType.SALES_COUNT)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 6 * * MON", zone = "Asia/Seoul")
|
||||||
|
fun refreshCommentCount() {
|
||||||
|
refresh(AudioRankingType.COMMENT_COUNT)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 7 * * MON", zone = "Asia/Seoul")
|
||||||
|
fun refreshLikeCount() {
|
||||||
|
refresh(AudioRankingType.LIKE_COUNT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refresh(type: AudioRankingType) {
|
||||||
|
val lockName = "lock:content-ranking-snapshot-refresh:$type"
|
||||||
|
val lock = redissonClient.getLock(lockName)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (lock.tryLock(0, -1, TimeUnit.SECONDS)) {
|
||||||
|
jobService.refreshLastCompletedWeekByScheduledJob(type)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (lock.isHeldByCurrentThread) {
|
||||||
|
lock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.content.ranking.adapter.out.scheduler
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.application.AudioRankingSnapshotJobService
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.redisson.api.RLock
|
||||||
|
import org.redisson.api.RedissonClient
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class AudioRankingSnapshotSchedulerTest {
|
||||||
|
@Test
|
||||||
|
fun shouldHaveDistributedMondayKstCronByRankingType() {
|
||||||
|
assertSchedule("refreshWeeklyPopular", "0 0 2 * * MON")
|
||||||
|
assertSchedule("refreshRising", "0 0 3 * * MON")
|
||||||
|
assertSchedule("refreshRevenue", "0 0 4 * * MON")
|
||||||
|
assertSchedule("refreshSalesCount", "0 0 5 * * MON")
|
||||||
|
assertSchedule("refreshCommentCount", "0 0 6 * * MON")
|
||||||
|
assertSchedule("refreshLikeCount", "0 0 7 * * MON")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldCallJobServiceOnlyWhenTypeLockAcquired() {
|
||||||
|
val jobService = Mockito.mock(AudioRankingSnapshotJobService::class.java)
|
||||||
|
val redissonClient = Mockito.mock(RedissonClient::class.java)
|
||||||
|
val lock = Mockito.mock(RLock::class.java)
|
||||||
|
Mockito.`when`(redissonClient.getLock("lock:content-ranking-snapshot-refresh:REVENUE")).thenReturn(lock)
|
||||||
|
Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(true)
|
||||||
|
Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(true)
|
||||||
|
val scheduler = AudioRankingSnapshotScheduler(jobService, redissonClient)
|
||||||
|
|
||||||
|
scheduler.refreshRevenue()
|
||||||
|
|
||||||
|
Mockito.verify(redissonClient).getLock("lock:content-ranking-snapshot-refresh:REVENUE")
|
||||||
|
Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS)
|
||||||
|
Mockito.verify(jobService).refreshLastCompletedWeekByScheduledJob(AudioRankingType.REVENUE)
|
||||||
|
Mockito.verify(lock).unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldSkipJobServiceWhenTypeLockIsNotAcquired() {
|
||||||
|
val jobService = Mockito.mock(AudioRankingSnapshotJobService::class.java)
|
||||||
|
val redissonClient = Mockito.mock(RedissonClient::class.java)
|
||||||
|
val lock = Mockito.mock(RLock::class.java)
|
||||||
|
Mockito.`when`(redissonClient.getLock("lock:content-ranking-snapshot-refresh:RISING")).thenReturn(lock)
|
||||||
|
Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(false)
|
||||||
|
Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(false)
|
||||||
|
val scheduler = AudioRankingSnapshotScheduler(jobService, redissonClient)
|
||||||
|
|
||||||
|
scheduler.refreshRising()
|
||||||
|
|
||||||
|
Mockito.verify(redissonClient).getLock("lock:content-ranking-snapshot-refresh:RISING")
|
||||||
|
Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS)
|
||||||
|
Mockito.verify(jobService, Mockito.never()).refreshLastCompletedWeekByScheduledJob(AudioRankingType.RISING)
|
||||||
|
Mockito.verify(lock, Mockito.never()).unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertSchedule(methodName: String, cron: String) {
|
||||||
|
val scheduled = AudioRankingSnapshotScheduler::class.java
|
||||||
|
.getDeclaredMethod(methodName)
|
||||||
|
.getAnnotation(Scheduled::class.java)
|
||||||
|
|
||||||
|
assertEquals(cron, scheduled.cron)
|
||||||
|
assertEquals("Asia/Seoul", scheduled.zone)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user