diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotScheduler.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotScheduler.kt index cfcdcf38..db113e22 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotScheduler.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotScheduler.kt @@ -1,6 +1,6 @@ package kr.co.vividnext.sodalive.v2.ranking.adapter.out.scheduler -import kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshService +import kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobService import org.redisson.api.RedissonClient import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Component @@ -8,7 +8,7 @@ import java.util.concurrent.TimeUnit @Component class CreatorRankingSnapshotScheduler( - private val refreshService: CreatorRankingSnapshotRefreshService, + private val jobService: CreatorRankingSnapshotJobService, private val redissonClient: RedissonClient ) { @Scheduled(cron = "0 30 7 * * MON", zone = "Asia/Seoul") @@ -18,7 +18,7 @@ class CreatorRankingSnapshotScheduler( try { if (lock.tryLock(0, -1, TimeUnit.SECONDS)) { - refreshService.refreshLastCompletedWeek() + jobService.refreshLastCompletedWeekByScheduledJob() } } finally { if (lock.isHeldByCurrentThread) { diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotSchedulerTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotSchedulerTest.kt new file mode 100644 index 00000000..42b2b7ef --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/adapter/out/scheduler/CreatorRankingSnapshotSchedulerTest.kt @@ -0,0 +1,72 @@ +package kr.co.vividnext.sodalive.v2.ranking.adapter.out.scheduler + +import kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobService +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +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 CreatorRankingSnapshotSchedulerTest { + @Test + @DisplayName("주간 스냅샷 스케줄러는 매주 월요일 07:30 KST cron으로 job 서비스를 호출한다") + fun shouldScheduleWeeklySnapshotRefreshAtKstMondaySevenThirty() { + val scheduled = CreatorRankingSnapshotScheduler::class.java + .getDeclaredMethod("refreshLastCompletedWeek") + .getAnnotation(Scheduled::class.java) + val service = Mockito.mock(CreatorRankingSnapshotJobService::class.java) + val redissonClient = Mockito.mock(RedissonClient::class.java) + val lock = Mockito.mock(RLock::class.java) + Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock) + Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(true) + Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(true) + val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient) + + scheduler.refreshLastCompletedWeek() + + assertEquals("0 30 7 * * MON", scheduled.cron) + assertEquals("Asia/Seoul", scheduled.zone) + Mockito.verify(service).refreshLastCompletedWeekByScheduledJob() + } + + @Test + @DisplayName("주간 스냅샷 스케줄러는 Redisson lock을 획득한 인스턴스만 갱신을 실행한다") + fun shouldRefreshLastCompletedWeekOnlyWhenRedissonLockAcquired() { + val service = Mockito.mock(CreatorRankingSnapshotJobService::class.java) + val redissonClient = Mockito.mock(RedissonClient::class.java) + val lock = Mockito.mock(RLock::class.java) + Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock) + Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(true) + Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(true) + val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient) + + scheduler.refreshLastCompletedWeek() + + Mockito.verify(redissonClient).getLock("lock:creator-ranking-snapshot-refresh") + Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS) + Mockito.verify(service).refreshLastCompletedWeekByScheduledJob() + Mockito.verify(lock).unlock() + } + + @Test + @DisplayName("주간 스냅샷 스케줄러는 Redisson lock 획득 실패 시 갱신을 건너뛴다") + fun shouldSkipLastCompletedWeekRefreshWhenRedissonLockNotAcquired() { + val service = Mockito.mock(CreatorRankingSnapshotJobService::class.java) + val redissonClient = Mockito.mock(RedissonClient::class.java) + val lock = Mockito.mock(RLock::class.java) + Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock) + Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(false) + Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(false) + val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient) + + scheduler.refreshLastCompletedWeek() + + Mockito.verify(redissonClient).getLock("lock:creator-ranking-snapshot-refresh") + Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS) + Mockito.verify(service, Mockito.never()).refreshLastCompletedWeekByScheduledJob() + Mockito.verify(lock, Mockito.never()).unlock() + } +} diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt index 25b8004b..a6de4469 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt @@ -1,6 +1,5 @@ package kr.co.vividnext.sodalive.v2.ranking.application -import kr.co.vividnext.sodalive.v2.ranking.adapter.out.scheduler.CreatorRankingSnapshotScheduler import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingSnapshotCandidate import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationPort import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationResult @@ -11,17 +10,12 @@ import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.Mockito -import org.redisson.api.RLock -import org.redisson.api.RedissonClient import org.springframework.boot.test.system.CapturedOutput import org.springframework.boot.test.system.OutputCaptureExtension -import org.springframework.scheduling.annotation.Scheduled import org.springframework.transaction.support.TransactionSynchronizationManager import java.time.LocalDateTime import java.time.ZoneId import java.time.ZonedDateTime -import java.util.concurrent.TimeUnit @ExtendWith(OutputCaptureExtension::class) class CreatorRankingSnapshotRefreshServiceTest { @@ -168,65 +162,6 @@ class CreatorRankingSnapshotRefreshServiceTest { assertEquals(true, output.out.contains("error=aggregate failed")) } - @Test - @DisplayName("주간 스냅샷 스케줄러는 매주 월요일 07:30 KST cron으로 갱신 서비스를 호출한다") - fun shouldScheduleWeeklySnapshotRefreshAtKstMondaySevenThirty() { - val scheduled = CreatorRankingSnapshotScheduler::class.java - .getDeclaredMethod("refreshLastCompletedWeek") - .getAnnotation(Scheduled::class.java) - val service = Mockito.mock(CreatorRankingSnapshotRefreshService::class.java) - val redissonClient = Mockito.mock(RedissonClient::class.java) - val lock = Mockito.mock(RLock::class.java) - Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock) - Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(true) - Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(true) - val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient) - - scheduler.refreshLastCompletedWeek() - - assertEquals("0 30 7 * * MON", scheduled.cron) - assertEquals("Asia/Seoul", scheduled.zone) - Mockito.verify(service).refreshLastCompletedWeek() - } - - @Test - @DisplayName("주간 스냅샷 스케줄러는 Redisson lock을 획득한 인스턴스만 갱신을 실행한다") - fun shouldRefreshLastCompletedWeekOnlyWhenRedissonLockAcquired() { - val service = Mockito.mock(CreatorRankingSnapshotRefreshService::class.java) - val redissonClient = Mockito.mock(RedissonClient::class.java) - val lock = Mockito.mock(RLock::class.java) - Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock) - Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(true) - Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(true) - val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient) - - scheduler.refreshLastCompletedWeek() - - Mockito.verify(redissonClient).getLock("lock:creator-ranking-snapshot-refresh") - Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS) - Mockito.verify(service).refreshLastCompletedWeek() - Mockito.verify(lock).unlock() - } - - @Test - @DisplayName("주간 스냅샷 스케줄러는 Redisson lock 획득 실패 시 갱신을 건너뛴다") - fun shouldSkipLastCompletedWeekRefreshWhenRedissonLockNotAcquired() { - val service = Mockito.mock(CreatorRankingSnapshotRefreshService::class.java) - val redissonClient = Mockito.mock(RedissonClient::class.java) - val lock = Mockito.mock(RLock::class.java) - Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock) - Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(false) - Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(false) - val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient) - - scheduler.refreshLastCompletedWeek() - - Mockito.verify(redissonClient).getLock("lock:creator-ranking-snapshot-refresh") - Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS) - Mockito.verify(service, Mockito.never()).refreshLastCompletedWeek() - Mockito.verify(lock, Mockito.never()).unlock() - } - private fun service( aggregationPort: CreatorRankingAggregationPort = FakeCreatorRankingAggregationPort(), snapshotPort: CreatorRankingSnapshotPort = FakeCreatorRankingSnapshotPort()