feat(creator): 오디오 탭 mapper를 추가한다

This commit is contained in:
2026-06-19 17:39:31 +09:00
parent d0843d94ed
commit 845b36828b
3 changed files with 277 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
package kr.co.vividnext.sodalive.v2.creator.channel.audio
import kr.co.vividnext.sodalive.v2.common.data.ContentSort
import kr.co.vividnext.sodalive.v2.creator.channel.audio.data.CreatorChannelAudioTabResponse
import kr.co.vividnext.sodalive.v2.creator.channel.audio.data.CreatorChannelAudioThemeResponse
import kr.co.vividnext.sodalive.v2.creator.channel.audio.model.CreatorChannelAudioContentStatus
import kr.co.vividnext.sodalive.v2.creator.channel.audio.model.CreatorChannelAudioRateUiModel
import kr.co.vividnext.sodalive.v2.creator.channel.audio.model.effectiveSelectedThemeId
import kr.co.vividnext.sodalive.v2.creator.channel.audio.model.toAudioContentUiModels
import kr.co.vividnext.sodalive.v2.creator.channel.audio.model.toRateUiModel
import kr.co.vividnext.sodalive.v2.creator.channel.audio.model.toThemeUiModels
import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelAudioContentResponse
import kr.co.vividnext.sodalive.v2.widget.AudioContentTag
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
class CreatorChannelAudioMapperTest {
@Test
fun `themes 응답 앞에 전체 tab을 추가하고 themeId null이면 전체를 선택한다`() {
val themes = audioResponse(themeId = null).toThemeUiModels()
assertEquals(listOf(null, 10L, 20L), themes.map { it.themeId })
assertEquals("전체", themes.first().title)
assertTrue(themes.first().isSelected)
}
@Test
fun `응답 themeId가 특정 id이면 해당 서버 theme tab만 selected 상태다`() {
val themes = audioResponse(themeId = 20L).toThemeUiModels()
assertEquals(listOf(false, false, true), themes.map { it.isSelected })
}
@Test
fun `응답 themeId가 서버 themes에 없으면 전체 tab을 selected 상태로 fallback한다`() {
val themes = audioResponse(themeId = 99L).toThemeUiModels()
assertEquals(listOf(true, false, false), themes.map { it.isSelected })
}
@Test
fun `응답 themeId가 서버 themes에 없으면 effective selected themeId는 null이다`() {
val response = audioResponse(themeId = 99L)
assertNull(response.effectiveSelectedThemeId())
}
@Test
fun `seriesName이 있으면 secondary text는 duration bullet seriesName이다`() {
val item = listOf(audioContent(duration = "10:00", seriesName = "시리즈명")).toAudioContentUiModels().single()
assertEquals("10:00 • 시리즈명", item.secondaryText)
}
@Test
fun `seriesName이 null 또는 blank이면 secondary text는 duration만 사용한다`() {
val items = listOf(
audioContent(audioContentId = 1L, duration = "10:00", seriesName = null),
audioContent(audioContentId = 2L, duration = "11:00", seriesName = " ")
).toAudioContentUiModels()
assertEquals(listOf("10:00", "11:00"), items.map { it.secondaryText })
}
@Test
fun `duration null item은 mapper 결과에서 제외한다`() {
val items = listOf(
audioContent(audioContentId = 1L, duration = null),
audioContent(audioContentId = 2L, duration = "10:00")
).toAudioContentUiModels()
assertEquals(listOf(2L), items.map { it.audioContentId })
}
@Test
fun `소장과 대여가 동시에 true이면 소장중 상태를 우선 매핑한다`() {
val item = listOf(audioContent(isOwned = true, isRented = true)).toAudioContentUiModels().single()
assertEquals(CreatorChannelAudioContentStatus.Owned, item.status)
}
@Test
fun `무료 콘텐츠는 무료 tag와 play CTA 상태로 매핑한다`() {
val item = listOf(audioContent(price = 0)).toAudioContentUiModels().single()
assertEquals(CreatorChannelAudioContentStatus.Play, item.status)
assertTrue(AudioContentTag.Free in item.tags)
}
@Test
fun `유료 미보유 콘텐츠는 price 상태로 매핑한다`() {
val item = listOf(audioContent(price = 500)).toAudioContentUiModels().single()
assertEquals(CreatorChannelAudioContentStatus.Price(500), item.status)
}
@Test
fun `성인 포인트 첫 콘텐츠 오리지널 시리즈 tag와 badge는 라이브 item 정책과 동일하게 매핑한다`() {
val item = listOf(
audioContent(
isAdult = true,
isPointAvailable = true,
isFirstContent = true,
isOriginalSeries = true
)
).toAudioContentUiModels().single()
assertTrue(item.showAdultBadge)
assertTrue(AudioContentTag.Point in item.tags)
assertTrue(AudioContentTag.First in item.tags)
assertTrue(AudioContentTag.Original in item.tags)
}
@Test
fun `소장률은 내 채널이 아니고 전체 테마일 때만 생성된다`() {
val response = audioResponse(themeId = null)
assertEquals(CreatorChannelAudioRateUiModel(75.0, 3, 4), response.toRateUiModel(isOwner = false))
assertNull(response.toRateUiModel(isOwner = true))
assertNull(audioResponse(themeId = 10L).toRateUiModel(isOwner = false))
assertEquals(CreatorChannelAudioRateUiModel(75.0, 3, 4), audioResponse(themeId = 99L).toRateUiModel(isOwner = false))
}
private fun audioResponse(themeId: Long?) = CreatorChannelAudioTabResponse(
audioContentCount = 1,
themes = listOf(
CreatorChannelAudioThemeResponse(themeId = 10L, themeName = "ASMR"),
CreatorChannelAudioThemeResponse(themeId = 20L, themeName = "수면")
),
themeId = themeId,
purchasedAudioContentRate = 75.0,
purchasedAudioContentCount = 3,
paidAudioContentCount = 4,
audioContents = listOf(audioContent()),
sort = ContentSort.LATEST,
page = 0,
size = 20,
hasNext = false
)
private fun audioContent(
audioContentId: Long = 1L,
price: Int = 10,
isPointAvailable: Boolean = false,
isFirstContent: Boolean = false,
seriesName: String? = null,
isOriginalSeries: Boolean? = false,
isAdult: Boolean = false,
isOwned: Boolean = false,
isRented: Boolean = false,
duration: String? = "10:00"
) = CreatorChannelAudioContentResponse(
audioContentId = audioContentId,
title = "오디오 $audioContentId",
duration = duration,
imageUrl = "https://example.com/audio.png",
price = price,
isPointAvailable = isPointAvailable,
isFirstContent = isFirstContent,
seriesName = seriesName,
isOriginalSeries = isOriginalSeries,
isAdult = isAdult,
isOwned = isOwned,
isRented = isRented
)
}