feat(banner): 구형 공개 조회에 예약 기간 필터를 적용한다
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
package kr.co.vividnext.sodalive.content
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||
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.BannerReservationCase
|
||||
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
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.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import org.springframework.context.annotation.Import
|
||||
import javax.persistence.EntityManager
|
||||
|
||||
@DataJpaTest(properties = ["spring.cache.type=none"])
|
||||
@Import(QueryDslConfig::class)
|
||||
class AudioContentRepositoryBannerTest @Autowired constructor(
|
||||
private val entityManager: EntityManager,
|
||||
queryFactory: JPAQueryFactory
|
||||
) {
|
||||
private var bannerNow = AudioContentBannerReservationFixtures.now
|
||||
private val repository = AudioContentQueryRepositoryImpl(queryFactory, "https://cdn.test") { bannerNow }
|
||||
|
||||
@Test
|
||||
@DisplayName("구형 공개 배너는 미래 및 만료 예약을 제외한다")
|
||||
fun shouldExcludeFutureAndExpiredReservations() {
|
||||
val now = bannerNow
|
||||
listOf("future", "expired", "visible").forEachIndexed { index, name ->
|
||||
entityManager.persist(
|
||||
AudioContentBanner(thumbnailImage = name, type = AudioContentBannerType.LINK, orders = index).apply {
|
||||
if (name == "future") startDate = now.plusYears(1).withNano(0)
|
||||
if (name == "expired") endDate = now.minusYears(1).withNano(0)
|
||||
}
|
||||
)
|
||||
}
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
|
||||
assertEquals(listOf("visible"), repository.getAudioContentMainBannerList(false).map { it.thumbnailImage })
|
||||
}
|
||||
|
||||
@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 banner = AudioContentBanner(thumbnailImage = "candidate", type = AudioContentBannerType.LINK).apply {
|
||||
startDate = case.startDate
|
||||
endDate = case.endDate
|
||||
}
|
||||
entityManager.persist(banner)
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
val stored = entityManager.find(AudioContentBanner::class.java, banner.id)
|
||||
assertEquals(case.startDate, stored.startDate)
|
||||
assertEquals(case.endDate, stored.endDate)
|
||||
|
||||
assertEquals(
|
||||
if (case.visible) listOf("candidate") else emptyList(),
|
||||
repository.getAudioContentMainBannerList(false).map { it.thumbnailImage }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("구형 공개 배너는 언어 제한 없이 기존 활성 성인 홈 탭 조건과 정렬을 유지한다")
|
||||
fun shouldPreserveActiveAdultHomeTabAndOrderFilters() {
|
||||
val tab = AudioContentMainTab(title = "tab", isActive = true)
|
||||
entityManager.persist(tab)
|
||||
listOf(
|
||||
AudioContentBanner("ko", AudioContentBannerType.LINK, Lang.KO, orders = 2),
|
||||
AudioContentBanner("ja", AudioContentBannerType.LINK, Lang.JA, orders = 1),
|
||||
AudioContentBanner("adult", AudioContentBannerType.LINK, isAdult = true, orders = 0),
|
||||
AudioContentBanner("inactive", AudioContentBannerType.LINK, isActive = false, orders = 0),
|
||||
AudioContentBanner("tab", AudioContentBannerType.LINK, orders = 0).apply { this.tab = tab }
|
||||
).forEach { entityManager.persist(it) }
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
|
||||
assertEquals(listOf("ja", "ko"), repository.getAudioContentMainBannerList(false).map { it.thumbnailImage })
|
||||
assertEquals(listOf("adult", "ja", "ko"), repository.getAudioContentMainBannerList(true).map { it.thumbnailImage })
|
||||
}
|
||||
}
|
||||
+88
-2
@@ -1,19 +1,105 @@
|
||||
package kr.co.vividnext.sodalive.content.main.banner
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
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.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import org.springframework.context.annotation.Import
|
||||
import javax.persistence.EntityManager
|
||||
|
||||
@DataJpaTest
|
||||
@DataJpaTest(properties = ["spring.cache.type=none"])
|
||||
@Import(QueryDslConfig::class)
|
||||
class AudioContentBannerRepositoryTest @Autowired constructor(
|
||||
private val repository: AudioContentBannerRepository
|
||||
private val repository: AudioContentBannerRepository,
|
||||
private val entityManager: EntityManager,
|
||||
queryFactory: JPAQueryFactory
|
||||
) {
|
||||
private var bannerNow = AudioContentBannerReservationFixtures.now
|
||||
private val queryRepository = AudioContentBannerQueryRepositoryImpl(queryFactory) { bannerNow }
|
||||
|
||||
@Test
|
||||
@DisplayName("공개 메인 배너는 미래 및 만료 예약을 제외한다")
|
||||
fun shouldExcludeFutureAndExpiredReservations() {
|
||||
val now = bannerNow
|
||||
listOf("future", "expired", "visible").forEachIndexed { index, name ->
|
||||
repository.save(
|
||||
AudioContentBanner(thumbnailImage = name, type = AudioContentBannerType.LINK, orders = index).apply {
|
||||
if (name == "future") startDate = now.plusYears(1).withNano(0)
|
||||
if (name == "expired") endDate = now.minusYears(1).withNano(0)
|
||||
}
|
||||
)
|
||||
}
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
|
||||
assertEquals(
|
||||
listOf("visible"),
|
||||
queryRepository.getAudioContentMainBannerList(1L, false).map { it.thumbnailImage }
|
||||
)
|
||||
}
|
||||
|
||||
@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 banner = repository.save(
|
||||
AudioContentBanner(thumbnailImage = "candidate", type = AudioContentBannerType.LINK).apply {
|
||||
startDate = case.startDate
|
||||
endDate = case.endDate
|
||||
}
|
||||
)
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
val stored = entityManager.find(AudioContentBanner::class.java, banner.id)
|
||||
assertEquals(case.startDate, stored.startDate)
|
||||
assertEquals(case.endDate, stored.endDate)
|
||||
|
||||
assertEquals(
|
||||
if (case.visible) listOf("candidate") else emptyList(),
|
||||
queryRepository.getAudioContentMainBannerList(1L, false).map { it.thumbnailImage }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("공개 메인 배너는 기존 탭 성인 활성 언어 조건과 정렬을 유지한다")
|
||||
fun shouldPreserveTabAdultActiveLanguageAndOrderFilters() {
|
||||
entityManager.createNativeQuery(
|
||||
"insert into audio_content_main_tab (id, title, is_active) values (2, 'tab', true)"
|
||||
).executeUpdate()
|
||||
val tab = entityManager.getReference(AudioContentMainTab::class.java, 2L)
|
||||
listOf(
|
||||
AudioContentBanner("ko", AudioContentBannerType.LINK, Lang.KO, orders = 3),
|
||||
AudioContentBanner("ja", AudioContentBannerType.LINK, Lang.JA, orders = 2),
|
||||
AudioContentBanner("adult", AudioContentBannerType.LINK, Lang.JA, isAdult = true, orders = 0),
|
||||
AudioContentBanner("inactive", AudioContentBannerType.LINK, Lang.JA, isActive = false, orders = 0),
|
||||
AudioContentBanner("tab", AudioContentBannerType.LINK, Lang.JA).apply { this.tab = tab },
|
||||
AudioContentBanner("future-tab", AudioContentBannerType.LINK, Lang.JA).apply {
|
||||
this.tab = tab
|
||||
startDate = bannerNow.plusDays(1)
|
||||
}
|
||||
).forEach { repository.save(it) }
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
|
||||
assertEquals(listOf("ja"), queryRepository.getAudioContentMainBannerList(1L, false, Lang.JA).map { it.thumbnailImage })
|
||||
assertEquals(
|
||||
listOf("adult", "ja"),
|
||||
queryRepository.getAudioContentMainBannerList(1L, true, Lang.JA).map { it.thumbnailImage }
|
||||
)
|
||||
assertEquals(listOf("ja", "ko"), queryRepository.getAudioContentMainBannerList(1L, false).map { it.thumbnailImage })
|
||||
assertEquals(listOf("tab"), queryRepository.getAudioContentMainBannerList(2L, false, Lang.JA).map { it.thumbnailImage })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("사용자 배너 조회는 요청 언어와 일치하는 배너만 반환한다")
|
||||
fun shouldReturnOnlyRequestedLanguageBanners() {
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package kr.co.vividnext.sodalive.content.main.banner
|
||||
|
||||
import java.time.LocalDateTime
|
||||
import java.util.stream.Stream
|
||||
|
||||
data class BannerReservationCase(
|
||||
val name: String,
|
||||
val now: LocalDateTime,
|
||||
val startDate: LocalDateTime?,
|
||||
val endDate: LocalDateTime?,
|
||||
val visible: Boolean
|
||||
) {
|
||||
override fun toString(): String = name
|
||||
}
|
||||
|
||||
object AudioContentBannerReservationFixtures {
|
||||
val now: LocalDateTime = LocalDateTime.of(2026, 10, 27, 23, 59, 59)
|
||||
|
||||
@JvmStatic
|
||||
fun cases(): Stream<BannerReservationCase> {
|
||||
val before = now.minusSeconds(1)
|
||||
val after = now.plusSeconds(1)
|
||||
return Stream.of(
|
||||
BannerReservationCase("unbounded", now, null, null, true),
|
||||
BannerReservationCase("start-before", now, before, null, true),
|
||||
BannerReservationCase("start-exact", now, now, null, true),
|
||||
BannerReservationCase("start-after", now, after, null, false),
|
||||
BannerReservationCase("end-before", now, null, before, false),
|
||||
BannerReservationCase("end-exact", now, null, now, true),
|
||||
BannerReservationCase("end-after", now, null, after, true),
|
||||
BannerReservationCase("inside", now, before, after, true),
|
||||
BannerReservationCase("before-range", now, after, after.plusSeconds(1), false),
|
||||
BannerReservationCase("after-range", now, before.minusSeconds(1), before, false),
|
||||
BannerReservationCase("both-exact", now, now, now, true),
|
||||
BannerReservationCase("end-second-123456789", now.withNano(123456789), before, now, true),
|
||||
BannerReservationCase("end-second-999999999", now.withNano(999999999), before, now, true),
|
||||
BannerReservationCase("next-second-not-started", now.withNano(999999999), after, null, false),
|
||||
BannerReservationCase("midnight-start-exact", after, after, null, true),
|
||||
BannerReservationCase("midnight-expired", after, null, now, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user