refactor(onair): 라이브 입장 흐름을 공통화한다
This commit is contained in:
@@ -9,32 +9,16 @@ import androidx.media3.common.util.UnstableApi
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.audio_content.AudioContentPlayService
|
||||
import kr.co.vividnext.sodalive.audio_content.player.AudioContentPlayerService
|
||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||
import kr.co.vividnext.sodalive.common.Constants
|
||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.common.ToastMessage
|
||||
import kr.co.vividnext.sodalive.databinding.ActivityHomeOnAirLiveBinding
|
||||
import kr.co.vividnext.sodalive.live.LiveViewModel
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailResponse
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomActivity
|
||||
import kr.co.vividnext.sodalive.live.room.dialog.LivePaymentDialog
|
||||
import kr.co.vividnext.sodalive.live.room.dialog.LiveRoomPasswordDialog
|
||||
import kr.co.vividnext.sodalive.settings.language.LanguageManager
|
||||
import kr.co.vividnext.sodalive.settings.language.LocaleHelper
|
||||
import kr.co.vividnext.sodalive.v2.access.AccessRequirement
|
||||
import kr.co.vividnext.sodalive.v2.access.ensureV2Access
|
||||
import kr.co.vividnext.sodalive.v2.live.action.LiveActionCoordinator
|
||||
import kr.co.vividnext.sodalive.v2.live.onair.model.HomeOnAirLivePageUiState
|
||||
import kr.co.vividnext.sodalive.v2.live.onair.model.canEnterHomeOnAirLiveRoom
|
||||
import kr.co.vividnext.sodalive.v2.live.onair.ui.HomeOnAirLiveAdapter
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
@UnstableApi
|
||||
class HomeOnAirLiveActivity : BaseActivity<ActivityHomeOnAirLiveBinding>(
|
||||
@@ -42,6 +26,16 @@ class HomeOnAirLiveActivity : BaseActivity<ActivityHomeOnAirLiveBinding>(
|
||||
) {
|
||||
private val viewModel: HomeOnAirLiveViewModel by viewModel()
|
||||
private val liveViewModel: LiveViewModel by inject()
|
||||
private val liveActionCoordinator: LiveActionCoordinator by lazy {
|
||||
LiveActionCoordinator(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
fragmentManager = supportFragmentManager,
|
||||
liveViewModel = liveViewModel,
|
||||
screenWidthProvider = { screenWidth },
|
||||
refreshHome = {}
|
||||
)
|
||||
}
|
||||
private val loadingDialog: LoadingDialog by lazy { LoadingDialog(this, layoutInflater) }
|
||||
private val adapter = HomeOnAirLiveAdapter { enterLiveRoom(it.roomId) }
|
||||
private var isPageLoading = false
|
||||
@@ -110,112 +104,9 @@ class HomeOnAirLiveActivity : BaseActivity<ActivityHomeOnAirLiveBinding>(
|
||||
}
|
||||
|
||||
private fun enterLiveRoom(roomId: Long) {
|
||||
ensureV2Access(AccessRequirement.Login) {
|
||||
liveViewModel.getRoomDetail(roomId) { roomDetail ->
|
||||
if (!canEnterHomeOnAirLiveRoom(roomDetail)) {
|
||||
liveActionCoordinator.enterLiveRoom(roomId) {
|
||||
Toast.makeText(applicationContext, R.string.common_error_unknown, Toast.LENGTH_LONG).show()
|
||||
return@getRoomDetail
|
||||
}
|
||||
|
||||
ensureV2Access(AccessRequirement.forAdultContent(roomDetail.isAdult)) {
|
||||
enterLiveRoom(roomId, roomDetail)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun enterLiveRoom(roomId: Long, roomDetail: GetRoomDetailResponse) {
|
||||
startService(
|
||||
Intent(applicationContext, AudioContentPlayService::class.java).apply {
|
||||
action = AudioContentPlayService.MusicAction.STOP.name
|
||||
}
|
||||
)
|
||||
startService(
|
||||
Intent(applicationContext, AudioContentPlayerService::class.java).apply {
|
||||
action = "STOP_SERVICE"
|
||||
}
|
||||
)
|
||||
|
||||
val onEnterRoomSuccess = {
|
||||
runOnUiThread {
|
||||
startActivity(
|
||||
Intent(applicationContext, LiveRoomActivity::class.java).apply {
|
||||
putExtra(Constants.EXTRA_ROOM_ID, roomId)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (roomDetail.manager.id == SharedPreferenceManager.userId) {
|
||||
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
||||
} else if (roomDetail.price == 0 || roomDetail.isPaid) {
|
||||
if (roomDetail.isPrivateRoom) {
|
||||
showPasswordDialog(roomId, can = 0, onEnterRoomSuccess = onEnterRoomSuccess)
|
||||
} else {
|
||||
liveViewModel.enterRoom(roomId, onEnterRoomSuccess)
|
||||
}
|
||||
} else {
|
||||
showPaidLiveEntryDialog(
|
||||
roomId = roomId,
|
||||
beginDateTimeUtc = roomDetail.beginDateTimeUtc,
|
||||
price = roomDetail.price,
|
||||
isPrivateRoom = roomDetail.isPrivateRoom,
|
||||
onEnterRoomSuccess = onEnterRoomSuccess
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showPasswordDialog(roomId: Long, can: Int, onEnterRoomSuccess: () -> Unit) {
|
||||
LiveRoomPasswordDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
can = can,
|
||||
confirmButtonClick = { password ->
|
||||
liveViewModel.enterRoom(
|
||||
roomId = roomId,
|
||||
onSuccess = onEnterRoomSuccess,
|
||||
password = password
|
||||
)
|
||||
}
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
private fun showPaidLiveEntryDialog(
|
||||
roomId: Long,
|
||||
beginDateTimeUtc: String,
|
||||
price: Int,
|
||||
isPrivateRoom: Boolean,
|
||||
onEnterRoomSuccess: () -> Unit
|
||||
) {
|
||||
if (isPrivateRoom) {
|
||||
showPasswordDialog(roomId, can = price, onEnterRoomSuccess = onEnterRoomSuccess)
|
||||
return
|
||||
}
|
||||
|
||||
val locale = Locale(LanguageManager.getEffectiveLanguage(this))
|
||||
val wrappedContext = LocaleHelper.wrap(this)
|
||||
val beginDate = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).apply {
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}.parse(beginDateTimeUtc) ?: return
|
||||
val now = Date()
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd, HH:mm", locale)
|
||||
val diffTime = now.time - beginDate.time
|
||||
val hours = (diffTime / (1000 * 60 * 60)).toInt()
|
||||
val mins = (diffTime / (1000 * 60)).toInt() % 60
|
||||
|
||||
LivePaymentDialog(
|
||||
activity = this,
|
||||
layoutInflater = layoutInflater,
|
||||
title = wrappedContext.getString(R.string.live_paid_title),
|
||||
startDateTime = if (hours >= 1) dateFormat.format(beginDate) else null,
|
||||
nowDateTime = if (hours >= 1) dateFormat.format(now) else null,
|
||||
desc = wrappedContext.getString(R.string.live_paid_desc, price),
|
||||
desc2 = if (hours >= 1) wrappedContext.getString(R.string.live_paid_warning, hours, mins) else null,
|
||||
confirmButtonTitle = wrappedContext.getString(R.string.live_paid_confirm),
|
||||
confirmButtonClick = { liveViewModel.enterRoom(roomId, onEnterRoomSuccess) },
|
||||
cancelButtonTitle = wrappedContext.getString(R.string.cancel),
|
||||
cancelButtonClick = {}
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
private fun showToast(toastMessage: ToastMessage) {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package kr.co.vividnext.sodalive.v2.live.onair.model
|
||||
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailResponse
|
||||
|
||||
fun canEnterHomeOnAirLiveRoom(roomDetail: GetRoomDetailResponse): Boolean {
|
||||
return roomDetail.channelName.isNullOrBlank().not()
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package kr.co.vividnext.sodalive.v2.live.action
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
class LiveActionCoordinatorSourceTest {
|
||||
|
||||
@Test
|
||||
fun `Live coordinator는 공통 정책과 기존 레거시 동작을 연결한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt"
|
||||
).readText()
|
||||
|
||||
assertTrue(source.contains("class LiveActionCoordinator("))
|
||||
assertTrue(source.contains("LiveEntryPolicy.decide("))
|
||||
assertTrue(source.contains("AccessRequirement.Login"))
|
||||
assertTrue(source.contains("val requiresAdultAccess ="))
|
||||
assertTrue(source.contains("requiresAdultContentAccess == true || roomDetail.isAdult"))
|
||||
assertTrue(source.contains("AccessRequirement.forAdultContent(requiresAdultAccess)"))
|
||||
assertTrue(source.contains("AudioContentPlayService.MusicAction.STOP.name"))
|
||||
assertTrue(source.contains("AudioContentPlayerService::class.java"))
|
||||
assertTrue(source.contains("LiveRoomPasswordDialog("))
|
||||
assertTrue(source.contains("LivePaymentDialog("))
|
||||
assertTrue(source.contains("LiveRoomDetailFragment("))
|
||||
assertTrue(source.contains("LiveRoomActivity::class.java"))
|
||||
assertFalse(source.contains("kr.co.vividnext.sodalive.v2.creator"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Live coordinator 기본 lifecycle predicate는 종료된 Activity 결과 처리를 막는다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt"
|
||||
).readText()
|
||||
|
||||
assertTrue(
|
||||
source.contains(
|
||||
"private val canHandleResult: () -> Boolean = " +
|
||||
"{ !activity.isFinishing && !activity.isDestroyed }"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Live coordinator는 입장 성공 callback 직전에 lifecycle을 다시 검사한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt"
|
||||
).readText()
|
||||
val entrySource = sourceSection(
|
||||
source,
|
||||
"fun enterLiveRoom(",
|
||||
"private fun stopAudioPlayback()"
|
||||
)
|
||||
val successSource = sourceSection(
|
||||
entrySource,
|
||||
"val onEnterRoomSuccess = {",
|
||||
"handleLiveEntry(roomId, roomDetail, decision, onEnterRoomSuccess)"
|
||||
)
|
||||
|
||||
assertTrue(successSource.contains("activity.runOnUiThread"))
|
||||
assertTrue(successSource.contains("runLiveActionUiSideEffect(::canHandleUiResult)"))
|
||||
assertBefore(
|
||||
successSource,
|
||||
"runLiveActionUiSideEffect(::canHandleUiResult)",
|
||||
"openLiveRoom(roomId)"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Live coordinator는 시작 성공 navigation 직전에도 lifecycle을 다시 검사한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt"
|
||||
).readText()
|
||||
val startSource = sourceSection(
|
||||
source,
|
||||
"private fun startLive(roomId: Long)",
|
||||
"private fun cancelLive(roomId: Long)"
|
||||
)
|
||||
|
||||
assertTrue(startSource.contains("activity.runOnUiThread"))
|
||||
assertBefore(
|
||||
startSource,
|
||||
"runLiveActionUiSideEffect(::canHandleUiResult)",
|
||||
"openLiveRoom(roomId)"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Live coordinator는 Activity stateSaved 호출 화면 predicate를 결합해 UI 결과를 검사한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt"
|
||||
).readText()
|
||||
val predicateSource = sourceSection(
|
||||
source,
|
||||
"private fun canHandleUiResult(): Boolean",
|
||||
"private companion object"
|
||||
)
|
||||
|
||||
assertTrue(predicateSource.contains("isActivityFinishing = activity.isFinishing"))
|
||||
assertTrue(predicateSource.contains("isActivityDestroyed = activity.isDestroyed"))
|
||||
assertTrue(predicateSource.contains("isStateSaved = fragmentManager.isStateSaved"))
|
||||
assertTrue(predicateSource.contains("canHandleResult = canHandleResult"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Live coordinator는 안정적인 tag와 state로 상세 중복 표시를 막는다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt"
|
||||
).readText()
|
||||
val detailSource = sourceSection(
|
||||
source,
|
||||
"fun showLiveRoomDetail(roomId: Long)",
|
||||
"fun enterLiveRoom("
|
||||
)
|
||||
|
||||
assertTrue(source.contains("LIVE_ROOM_DETAIL_TAG"))
|
||||
assertTrue(detailSource.contains("fragmentManager.findFragmentByTag(LIVE_ROOM_DETAIL_TAG)"))
|
||||
assertTrue(detailSource.contains("canShowLiveRoomDetail("))
|
||||
assertTrue(detailSource.contains("detailFragment.showNow(fragmentManager, LIVE_ROOM_DETAIL_TAG)"))
|
||||
assertBefore(detailSource, "canShowLiveRoomDetail(", "LiveRoomDetailFragment(")
|
||||
assertBefore(
|
||||
detailSource,
|
||||
"fragmentManager.findFragmentByTag(LIVE_ROOM_DETAIL_TAG)",
|
||||
"LiveRoomDetailFragment("
|
||||
)
|
||||
assertFalse(detailSource.contains("detailFragment.isAdded"))
|
||||
assertFalse(detailSource.contains("detailFragment.tag"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Live coordinator 공개 상세 진입점은 로그인과 성인 접근을 확인한 뒤 표시한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt"
|
||||
).readText()
|
||||
val publicDetailSource = sourceSection(
|
||||
source,
|
||||
"fun showLiveRoomDetail(roomId: Long)",
|
||||
"private fun showLiveRoomDetailContent(roomId: Long)"
|
||||
)
|
||||
|
||||
assertTrue(publicDetailSource.contains("AccessRequirement.Login"))
|
||||
assertTrue(publicDetailSource.contains("liveViewModel.getRoomDetail(roomId)"))
|
||||
assertTrue(publicDetailSource.contains("AccessRequirement.forAdultContent(roomDetail.isAdult)"))
|
||||
assertTrue(publicDetailSource.contains("showLiveRoomDetailContent(roomId)"))
|
||||
assertBefore(publicDetailSource, "AccessRequirement.Login", "liveViewModel.getRoomDetail(roomId)")
|
||||
assertBefore(
|
||||
publicDetailSource,
|
||||
"AccessRequirement.forAdultContent(roomDetail.isAdult)",
|
||||
"showLiveRoomDetailContent(roomId)"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Home Creator Channel On-air는 동일한 Live coordinator를 사용한다`() {
|
||||
val home = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt"
|
||||
).readText()
|
||||
val creator = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
|
||||
).readText()
|
||||
val onAir = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt"
|
||||
).readText()
|
||||
|
||||
assertTrue(home.contains("private val liveActionCoordinator: LiveActionCoordinator by lazy"))
|
||||
assertTrue(home.contains("liveActionCoordinator.enterLiveRoom(item.roomId)"))
|
||||
assertFalse(home.contains("liveViewModel.getRoomDetail(item.roomId)"))
|
||||
|
||||
assertTrue(creator.contains("private val liveActionCoordinator: LiveActionCoordinator by lazy"))
|
||||
assertTrue(
|
||||
creator.contains(
|
||||
"liveActionCoordinator.enterLiveRoom(live.liveId, requiresAdultContentAccess = live.isAdult)"
|
||||
)
|
||||
)
|
||||
|
||||
assertTrue(onAir.contains("private val liveActionCoordinator: LiveActionCoordinator by lazy"))
|
||||
assertTrue(onAir.contains("liveActionCoordinator.enterLiveRoom(roomId"))
|
||||
assertFalse(onAir.contains("private fun enterLiveRoom(roomId: Long, roomDetail:"))
|
||||
assertFalse(onAir.contains("private fun showPaidLiveEntryDialog("))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Creator Channel은 라이브 생성 결과를 단일 handler에서 처리한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt"
|
||||
).readText()
|
||||
val handlerStartIndex = source.indexOf(
|
||||
"private fun handleLiveCreationResult(result: LiveCreationResult)"
|
||||
)
|
||||
val handlerEndIndex = source.indexOf(
|
||||
"override val shouldApplySystemBarTopInset",
|
||||
handlerStartIndex
|
||||
)
|
||||
|
||||
assertTrue(source.contains("resolveLiveCreationResult("))
|
||||
assertTrue(handlerStartIndex >= 0)
|
||||
assertTrue(handlerEndIndex > handlerStartIndex)
|
||||
|
||||
val handlerSource = source.substring(handlerStartIndex, handlerEndIndex)
|
||||
|
||||
assertTrue(handlerSource.contains("LiveCreationResult.Ignored -> Unit"))
|
||||
assertTrue(
|
||||
handlerSource.contains(
|
||||
"LiveCreationResult.RefreshOnly -> homeActionDelegate?.refreshHome()"
|
||||
)
|
||||
)
|
||||
assertTrue(handlerSource.contains("LiveCreationResult.Created ->"))
|
||||
assertTrue(handlerSource.contains("is LiveCreationResult.Enter ->"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Live coordinator는 unavailable을 adult guard와 오디오 중단보다 먼저 처리한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt"
|
||||
).readText()
|
||||
val flowSource = sourceSection(
|
||||
source,
|
||||
"runLiveEntryDecisionFlow(",
|
||||
"val loadRoomDetail ="
|
||||
)
|
||||
val unavailableIndex = flowSource.indexOf(
|
||||
"handleUnavailableLive(roomId, requiresAdultAccess, onUnavailable)"
|
||||
)
|
||||
val adultGuardIndex = flowSource.indexOf(
|
||||
"AccessRequirement.forAdultContent(requiresAdultAccess)"
|
||||
)
|
||||
val stopAudioIndex = flowSource.indexOf("stopAudioPlayback()")
|
||||
|
||||
assertTrue(unavailableIndex >= 0)
|
||||
assertTrue(adultGuardIndex >= 0)
|
||||
assertTrue(stopAudioIndex >= 0)
|
||||
assertTrue(unavailableIndex < adultGuardIndex)
|
||||
assertTrue(adultGuardIndex < stopAudioIndex)
|
||||
assertFalse(source.contains("AccessRequirement.forAdultContent(requiresAdultContentAccess),"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Live coordinator는 unavailable 상세 fallback만 adult guard 뒤에 표시한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/action/LiveActionCoordinator.kt"
|
||||
).readText()
|
||||
|
||||
val effectiveAdultAccess = "requiresAdultContentAccess == true || roomDetail.isAdult"
|
||||
val fallbackStartIndex = source.indexOf("private fun handleUnavailableLive(")
|
||||
val fallbackEndIndex = source.indexOf("private fun reservationRoom(", fallbackStartIndex)
|
||||
|
||||
assertTrue(source.contains("val requiresAdultAccess = $effectiveAdultAccess"))
|
||||
assertTrue(source.indexOf(effectiveAdultAccess) == source.lastIndexOf(effectiveAdultAccess))
|
||||
assertTrue(
|
||||
source.contains("handleUnavailableLive(roomId, requiresAdultAccess, onUnavailable)")
|
||||
)
|
||||
assertTrue(fallbackStartIndex >= 0)
|
||||
assertTrue(fallbackEndIndex > fallbackStartIndex)
|
||||
|
||||
val fallbackSource = source.substring(fallbackStartIndex, fallbackEndIndex)
|
||||
val fallbackGuardIndex = fallbackSource.indexOf(
|
||||
"AccessRequirement.forAdultContent(requiresAdultAccess)"
|
||||
)
|
||||
val callbackIndex = fallbackSource.indexOf("onUnavailable()")
|
||||
val callbackReturnIndex = fallbackSource.indexOf("return", callbackIndex)
|
||||
val showDetailIndex = fallbackSource.indexOf("showLiveRoomDetailContent(roomId)")
|
||||
|
||||
assertTrue(fallbackSource.contains("requiresAdultAccess: Boolean"))
|
||||
assertTrue(fallbackSource.contains("if (onUnavailable != null)"))
|
||||
assertFalse(fallbackSource.contains("roomDetail.isAdult"))
|
||||
assertTrue(callbackIndex >= 0)
|
||||
assertTrue(callbackReturnIndex >= 0)
|
||||
assertTrue(fallbackGuardIndex >= 0)
|
||||
assertTrue(showDetailIndex >= 0)
|
||||
assertTrue(callbackIndex < callbackReturnIndex)
|
||||
assertTrue(callbackReturnIndex < fallbackGuardIndex)
|
||||
assertTrue(fallbackGuardIndex < showDetailIndex)
|
||||
}
|
||||
|
||||
private fun projectFile(relativePath: String): File {
|
||||
val candidates = listOf(File(relativePath), File("../$relativePath"))
|
||||
return candidates.firstOrNull { it.exists() }
|
||||
?: error("Project file not found: $relativePath")
|
||||
}
|
||||
|
||||
private fun sourceSection(source: String, startMarker: String, endMarker: String): String {
|
||||
val startIndex = source.indexOf(startMarker)
|
||||
assertTrue("Missing start marker: $startMarker", startIndex >= 0)
|
||||
val endIndex = source.indexOf(endMarker, startIndex + startMarker.length)
|
||||
assertTrue("Missing end marker: $endMarker", endIndex > startIndex)
|
||||
return source.substring(startIndex, endIndex)
|
||||
}
|
||||
|
||||
private fun assertBefore(source: String, expectedBefore: String, expectedAfter: String) {
|
||||
val beforeIndex = source.indexOf(expectedBefore)
|
||||
val afterIndex = source.indexOf(expectedAfter)
|
||||
assertTrue("Missing expected source: $expectedBefore", beforeIndex >= 0)
|
||||
assertTrue("Missing expected source: $expectedAfter", afterIndex >= 0)
|
||||
assertTrue("Expected '$expectedBefore' before '$expectedAfter'", beforeIndex < afterIndex)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.v2.live.onair
|
||||
|
||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailManager
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailResponse
|
||||
import kr.co.vividnext.sodalive.v2.live.onair.model.canEnterHomeOnAirLiveRoom
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
@@ -12,72 +8,24 @@ import java.io.File
|
||||
class HomeOnAirLiveEntryPolicyTest {
|
||||
|
||||
@Test
|
||||
fun `channelName이 있으면 입장 분기를 허용한다`() {
|
||||
assertTrue(canEnterHomeOnAirLiveRoom(roomDetail(channelName = "channel-a")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `channelName이 null이면 입장 분기를 중단한다`() {
|
||||
assertFalse(canEnterHomeOnAirLiveRoom(roomDetail(channelName = null)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `channelName이 blank이면 입장 분기를 중단한다`() {
|
||||
assertFalse(canEnterHomeOnAirLiveRoom(roomDetail(channelName = " ")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `라이브 입장은 로그인 상세 조회 성인 가드 기존 입장 순서를 유지한다`() {
|
||||
fun `On-air 라이브 입장은 공통 Live Action과 unavailable Toast를 연결한다`() {
|
||||
val source = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/live/onair/HomeOnAirLiveActivity.kt"
|
||||
).readText()
|
||||
val entrySource = sourceSection(
|
||||
source = source,
|
||||
startMarker = "private fun enterLiveRoom(roomId: Long) {",
|
||||
endMarker = "private fun enterLiveRoom(roomId: Long, roomDetail: GetRoomDetailResponse)"
|
||||
endMarker = "private fun showToast(toastMessage: ToastMessage)"
|
||||
)
|
||||
val loginGuard = "ensureV2Access(AccessRequirement.Login)"
|
||||
val roomDetailQuery = "liveViewModel.getRoomDetail(roomId) { roomDetail ->"
|
||||
val adultGuard = "ensureV2Access(AccessRequirement.forAdultContent(roomDetail.isAdult))"
|
||||
val enterRoom = "enterLiveRoom(roomId, roomDetail)"
|
||||
|
||||
assertBefore(entrySource, loginGuard, roomDetailQuery)
|
||||
assertBefore(entrySource, roomDetailQuery, adultGuard)
|
||||
assertBefore(entrySource, adultGuard, enterRoom)
|
||||
assertFalse(source.contains("private fun ensureLoginAndAdultAuth"))
|
||||
assertTrue(source.contains("private val liveActionCoordinator: LiveActionCoordinator by lazy"))
|
||||
assertTrue(entrySource.contains("liveActionCoordinator.enterLiveRoom(roomId)"))
|
||||
assertTrue(entrySource.contains("R.string.common_error_unknown"))
|
||||
assertFalse(source.contains("private fun enterLiveRoom(roomId: Long, roomDetail:"))
|
||||
assertFalse(source.contains("private fun showPaidLiveEntryDialog("))
|
||||
assertFalse(source.contains("private fun showPasswordDialog("))
|
||||
}
|
||||
|
||||
private fun roomDetail(channelName: String?) = GetRoomDetailResponse(
|
||||
roomId = 1L,
|
||||
price = 0,
|
||||
title = "title",
|
||||
notice = "notice",
|
||||
isPaid = false,
|
||||
isAdult = false,
|
||||
genderRestriction = GenderRestriction.ALL,
|
||||
isPrivateRoom = false,
|
||||
password = null,
|
||||
tags = emptyList(),
|
||||
channelName = channelName,
|
||||
beginDateTimeUtc = "2026-06-26T12:00:00",
|
||||
isNotification = false,
|
||||
numberOfParticipants = 1,
|
||||
numberOfParticipantsTotal = 10,
|
||||
manager = GetRoomDetailManager(
|
||||
id = 100L,
|
||||
nickname = "creator",
|
||||
introduce = "",
|
||||
youtubeUrl = null,
|
||||
instagramUrl = null,
|
||||
kakaoOpenChatUrl = null,
|
||||
fancimmUrl = null,
|
||||
xUrl = null,
|
||||
profileImageUrl = "",
|
||||
isCreator = true
|
||||
),
|
||||
participatingUsers = emptyList()
|
||||
)
|
||||
|
||||
private fun sourceSection(source: String, startMarker: String, endMarker: String): String {
|
||||
val start = source.indexOf(startMarker)
|
||||
val end = source.indexOf(endMarker, start)
|
||||
@@ -88,14 +36,6 @@ class HomeOnAirLiveEntryPolicyTest {
|
||||
return source.substring(start, end)
|
||||
}
|
||||
|
||||
private fun assertBefore(source: String, expectedBefore: String, expectedAfter: String) {
|
||||
val beforeIndex = source.indexOf(expectedBefore)
|
||||
val afterIndex = source.indexOf(expectedAfter, beforeIndex)
|
||||
|
||||
assertTrue("missing source: $expectedBefore", beforeIndex >= 0)
|
||||
assertTrue("missing source after $expectedBefore: $expectedAfter", afterIndex > beforeIndex)
|
||||
}
|
||||
|
||||
private fun projectFile(relativePath: String): File {
|
||||
val candidates = listOf(File(relativePath), File("../$relativePath"))
|
||||
return candidates.firstOrNull { it.exists() }
|
||||
|
||||
Reference in New Issue
Block a user