feat(banner): v2 콘텐츠 추천에 예약 기간 필터를 적용한다
This commit is contained in:
+5
-1
@@ -10,6 +10,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory
|
|||||||
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner
|
import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.isVisibleAt
|
||||||
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
||||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
|
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||||
@@ -34,6 +35,7 @@ import org.springframework.stereotype.Repository
|
|||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
import javax.persistence.EntityManager
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
@@ -42,7 +44,8 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
private val entityManager: EntityManager,
|
private val entityManager: EntityManager,
|
||||||
private val contentTranslationRepository: ContentTranslationRepository,
|
private val contentTranslationRepository: ContentTranslationRepository,
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String,
|
||||||
|
private val nowProvider: () -> LocalDateTime = { LocalDateTime.now(ZoneOffset.UTC) }
|
||||||
) : AudioRecommendationQueryRepository {
|
) : AudioRecommendationQueryRepository {
|
||||||
override fun findBanners(
|
override fun findBanners(
|
||||||
limit: Int,
|
limit: Int,
|
||||||
@@ -74,6 +77,7 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
audioContentBanner.isActive.isTrue,
|
audioContentBanner.isActive.isTrue,
|
||||||
audioContentBanner.tab.id.eq(2L),
|
audioContentBanner.tab.id.eq(2L),
|
||||||
audioContentBanner.lang.eq(lang),
|
audioContentBanner.lang.eq(lang),
|
||||||
|
audioContentBanner.isVisibleAt(nowProvider()),
|
||||||
adultBannerCondition(canViewAdultContent),
|
adultBannerCondition(canViewAdultContent),
|
||||||
activeBannerTargetCondition(memberId, bannerCreator, seriesOwner)
|
activeBannerTargetCondition(memberId, bannerCreator, seriesOwner)
|
||||||
)
|
)
|
||||||
|
|||||||
+59
-1
@@ -7,7 +7,9 @@ import kr.co.vividnext.sodalive.content.AudioContent
|
|||||||
import kr.co.vividnext.sodalive.content.comment.AudioContentComment
|
import kr.co.vividnext.sodalive.content.comment.AudioContentComment
|
||||||
import kr.co.vividnext.sodalive.content.like.AudioContentLike
|
import kr.co.vividnext.sodalive.content.like.AudioContentLike
|
||||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerReservationFixtures
|
||||||
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.BannerReservationCase
|
||||||
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
|
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
|
||||||
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslation
|
import kr.co.vividnext.sodalive.content.translation.ContentTranslation
|
||||||
@@ -26,6 +28,8 @@ import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
|
|||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||||
import org.springframework.context.annotation.Import
|
import org.springframework.context.annotation.Import
|
||||||
@@ -44,12 +48,55 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
queryFactory: JPAQueryFactory,
|
queryFactory: JPAQueryFactory,
|
||||||
contentTranslationRepository: ContentTranslationRepository
|
contentTranslationRepository: ContentTranslationRepository
|
||||||
) {
|
) {
|
||||||
|
private var bannerNow = AudioContentBannerReservationFixtures.now
|
||||||
private val repository = DefaultAudioRecommendationQueryRepository(
|
private val repository = DefaultAudioRecommendationQueryRepository(
|
||||||
queryFactory,
|
queryFactory,
|
||||||
entityManager,
|
entityManager,
|
||||||
contentTranslationRepository,
|
contentTranslationRepository,
|
||||||
"https://cdn.test"
|
"https://cdn.test"
|
||||||
)
|
) { bannerNow }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("콘텐츠 추천 배너는 limit 전에 미래 및 만료 예약을 제외한다")
|
||||||
|
fun shouldExcludeFutureAndExpiredReservationsBeforeLimit() {
|
||||||
|
val now = bannerNow
|
||||||
|
val tab = saveContentRecommendationTab()
|
||||||
|
listOf("future", "expired", "visible-1", "visible-2").forEachIndexed { index, name ->
|
||||||
|
saveBanner(name, AudioContentBannerType.LINK, index, tab = tab).apply {
|
||||||
|
if (name == "future") startDate = now.plusYears(1).withNano(0)
|
||||||
|
if (name == "expired") endDate = now.minusYears(1).withNano(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flushAndClear()
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf("https://cdn.test/visible-1", "https://cdn.test/visible-2"),
|
||||||
|
repository.findBanners(2, null, false).map { it.imageUrl }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}")
|
||||||
|
@MethodSource("kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerReservationFixtures#cases")
|
||||||
|
@DisplayName("콘텐츠 추천 배너는 UTC 초 정밀도의 예약 경계를 포함한다")
|
||||||
|
fun shouldApplyReservationBoundaries(case: BannerReservationCase) {
|
||||||
|
bannerNow = case.now
|
||||||
|
entityManager.createNativeQuery("alter table content_banner alter column start_date timestamp(0)").executeUpdate()
|
||||||
|
entityManager.createNativeQuery("alter table content_banner alter column end_date timestamp(0)").executeUpdate()
|
||||||
|
val tab = saveContentRecommendationTab()
|
||||||
|
val banner = saveBanner("candidate", AudioContentBannerType.LINK, 1, tab = tab).apply {
|
||||||
|
startDate = case.startDate
|
||||||
|
endDate = case.endDate
|
||||||
|
}
|
||||||
|
flushAndClear()
|
||||||
|
val stored = entityManager.find(AudioContentBanner::class.java, banner.id)
|
||||||
|
assertEquals(case.startDate, stored.startDate)
|
||||||
|
assertEquals(case.endDate, stored.endDate)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
if (case.visible) listOf("https://cdn.test/candidate") else emptyList(),
|
||||||
|
repository.findBanners(2, null, false).map { it.imageUrl }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("배너는 콘텐츠 추천 탭과 성인 조회 가능 여부를 적용한다")
|
@DisplayName("배너는 콘텐츠 추천 탭과 성인 조회 가능 여부를 적용한다")
|
||||||
@@ -57,6 +104,8 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
val viewer = saveMember("viewer", MemberRole.USER)
|
val viewer = saveMember("viewer", MemberRole.USER)
|
||||||
val visibleCreator = saveMember("visible-creator", MemberRole.CREATOR)
|
val visibleCreator = saveMember("visible-creator", MemberRole.CREATOR)
|
||||||
val blockedCreator = saveMember("blocked-creator", MemberRole.CREATOR)
|
val blockedCreator = saveMember("blocked-creator", MemberRole.CREATOR)
|
||||||
|
val reverseBlockedCreator = saveMember("reverse-blocked-creator", MemberRole.CREATOR)
|
||||||
|
val inactiveCreator = saveMember("inactive-banner-creator", MemberRole.CREATOR, isActive = false)
|
||||||
val recommendationTab = saveContentRecommendationTab()
|
val recommendationTab = saveContentRecommendationTab()
|
||||||
val visibleBanner = saveBanner(
|
val visibleBanner = saveBanner(
|
||||||
"visible.png",
|
"visible.png",
|
||||||
@@ -89,7 +138,16 @@ class DefaultAudioRecommendationQueryRepositoryTest @Autowired constructor(
|
|||||||
tab = recommendationTab
|
tab = recommendationTab
|
||||||
)
|
)
|
||||||
saveBanner("home.png", AudioContentBannerType.LINK, 1, link = "https://home.test")
|
saveBanner("home.png", AudioContentBannerType.LINK, 1, link = "https://home.test")
|
||||||
|
saveBanner("inactive-target.png", AudioContentBannerType.CREATOR, 0, creator = inactiveCreator, tab = recommendationTab)
|
||||||
|
saveBanner(
|
||||||
|
"reverse-blocked.png",
|
||||||
|
AudioContentBannerType.CREATOR,
|
||||||
|
0,
|
||||||
|
creator = reverseBlockedCreator,
|
||||||
|
tab = recommendationTab
|
||||||
|
)
|
||||||
saveBlock(viewer, blockedCreator)
|
saveBlock(viewer, blockedCreator)
|
||||||
|
saveBlock(reverseBlockedCreator, viewer)
|
||||||
flushAndClear()
|
flushAndClear()
|
||||||
|
|
||||||
val safeBanners = repository.findBanners(limit = 20, memberId = viewer.id, canViewAdultContent = false)
|
val safeBanners = repository.findBanners(limit = 20, memberId = viewer.id, canViewAdultContent = false)
|
||||||
|
|||||||
Reference in New Issue
Block a user