test #426
@@ -0,0 +1,64 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.home.live.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
|
import kr.co.vividnext.sodalive.v2.api.home.live.dto.HomeOnAirLivePageResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.api.home.live.dto.HomeOnAirLiveResponse
|
||||||
|
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
|
||||||
|
import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeLiveRecommendationRecord
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class HomeOnAirLiveFacade(
|
||||||
|
private val queryService: HomeRecommendationQueryService,
|
||||||
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
|
) {
|
||||||
|
fun getOnAirLives(member: Member, page: Int): HomeOnAirLivePageResponse {
|
||||||
|
val normalizedPage = page.coerceIn(0, MAX_PAGE)
|
||||||
|
val fetched = queryService.findLiveRecommendations(
|
||||||
|
offset = normalizedPage * PAGE_SIZE,
|
||||||
|
limit = PAGE_SIZE + 1,
|
||||||
|
memberId = member.id,
|
||||||
|
includeAdultLives = memberContentPreferenceService.canViewAdultContent(member)
|
||||||
|
)
|
||||||
|
val items = fetched.take(PAGE_SIZE).map { it.toResponse() }
|
||||||
|
|
||||||
|
return HomeOnAirLivePageResponse(
|
||||||
|
items = items,
|
||||||
|
page = normalizedPage,
|
||||||
|
size = PAGE_SIZE,
|
||||||
|
hasNext = fetched.size > PAGE_SIZE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun HomeLiveRecommendationRecord.toResponse() = HomeOnAirLiveResponse(
|
||||||
|
roomId = liveRoomId,
|
||||||
|
creatorNickname = creatorNickname,
|
||||||
|
creatorProfileImage = profileImageUrl(creatorProfileImage),
|
||||||
|
title = title,
|
||||||
|
price = price,
|
||||||
|
beginDateTimeUtc = beginDateTime.toUtcIso()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun profileImageUrl(path: String?): String {
|
||||||
|
return imageUrl(path) ?: "$cloudFrontHost/profile/default-profile.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun imageUrl(path: String?): String? {
|
||||||
|
return if (path.isNullOrBlank()) null else "$cloudFrontHost/$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LocalDateTime.toUtcIso(): String {
|
||||||
|
return atOffset(ZoneOffset.UTC).toInstant().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PAGE_SIZE = 20
|
||||||
|
private const val MAX_PAGE = 10_000
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.api.home.live.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
|
import kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService
|
||||||
|
import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeLiveRecommendationRecord
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
class HomeOnAirLiveFacadeTest {
|
||||||
|
private val queryService = Mockito.mock(HomeRecommendationQueryService::class.java)
|
||||||
|
private val preferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
||||||
|
private val facade = HomeOnAirLiveFacade(queryService, preferenceService, "https://cdn.test")
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldReturnFixedSizePageAndHasNext() {
|
||||||
|
val member = createMember(100L)
|
||||||
|
Mockito.doReturn(true).`when`(preferenceService).canViewAdultContent(member)
|
||||||
|
Mockito.doReturn((1L..21L).map { record(it) }).`when`(queryService).findLiveRecommendations(
|
||||||
|
eqValue(0),
|
||||||
|
eqValue(21),
|
||||||
|
eqValue(member.id),
|
||||||
|
eqValue(true)
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = facade.getOnAirLives(member, page = 0)
|
||||||
|
|
||||||
|
assertEquals(0, response.page)
|
||||||
|
assertEquals(20, response.size)
|
||||||
|
assertEquals(true, response.hasNext)
|
||||||
|
assertEquals(20, response.items.size)
|
||||||
|
Mockito.verify(queryService).findLiveRecommendations(eqValue(0), eqValue(21), eqValue(member.id), eqValue(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldUseDefaultProfileImageWhenCreatorProfileImageIsBlank() {
|
||||||
|
val member = createMember(100L)
|
||||||
|
Mockito.doReturn(false).`when`(preferenceService).canViewAdultContent(member)
|
||||||
|
Mockito.doReturn(listOf(record(1L, creatorProfileImage = null))).`when`(queryService).findLiveRecommendations(
|
||||||
|
eqValue(0),
|
||||||
|
eqValue(21),
|
||||||
|
eqValue(member.id),
|
||||||
|
eqValue(false)
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = facade.getOnAirLives(member, page = 0)
|
||||||
|
|
||||||
|
assertEquals("https://cdn.test/profile/default-profile.png", response.items.single().creatorProfileImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldMapBeginDateTimeToUtcIsoString() {
|
||||||
|
val member = createMember(100L)
|
||||||
|
Mockito.doReturn(false).`when`(preferenceService).canViewAdultContent(member)
|
||||||
|
Mockito.doReturn(listOf(record(1L, beginDateTime = LocalDateTime.of(2026, 6, 26, 12, 30)))).`when`(queryService)
|
||||||
|
.findLiveRecommendations(eqValue(0), eqValue(21), eqValue(member.id), eqValue(false))
|
||||||
|
|
||||||
|
val response = facade.getOnAirLives(member, page = 0)
|
||||||
|
|
||||||
|
assertEquals("2026-06-26T12:30:00Z", response.items.single().beginDateTimeUtc)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun record(
|
||||||
|
id: Long,
|
||||||
|
creatorProfileImage: String? = "profile.png",
|
||||||
|
beginDateTime: LocalDateTime = LocalDateTime.of(2026, 6, 26, 12, 30)
|
||||||
|
) = HomeLiveRecommendationRecord(
|
||||||
|
liveRoomId = id,
|
||||||
|
creatorNickname = "creator-$id",
|
||||||
|
creatorProfileImage = creatorProfileImage,
|
||||||
|
title = "live-$id",
|
||||||
|
price = id.toInt(),
|
||||||
|
beginDateTime = beginDateTime
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createMember(id: Long): Member {
|
||||||
|
return Member(
|
||||||
|
email = "viewer$id@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = "viewer$id",
|
||||||
|
role = MemberRole.USER
|
||||||
|
).apply { this.id = id }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> eqValue(value: T): T {
|
||||||
|
return Mockito.eq(value) ?: value
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user