feat(live): 생성 결과 계약을 추가한다

This commit is contained in:
2026-07-15 21:26:53 +09:00
parent b582545991
commit c223b3b8a4
3 changed files with 220 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
package kr.co.vividnext.sodalive.v2.live.action
sealed interface LiveCreationResult {
data object Ignored : LiveCreationResult
data object Created : LiveCreationResult
data object RefreshOnly : LiveCreationResult
data class Enter(val roomId: Long) : LiveCreationResult
}
fun resolveLiveCreationResult(
isSuccessful: Boolean,
roomId: Long?,
channelName: String?
): LiveCreationResult = when {
!isSuccessful -> LiveCreationResult.Ignored
channelName == null -> LiveCreationResult.Created
roomId != null && roomId > 0L -> LiveCreationResult.Enter(roomId)
else -> LiveCreationResult.RefreshOnly
}

View File

@@ -0,0 +1,153 @@
package kr.co.vividnext.sodalive.v2.live.action
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class LiveActionCoordinatorUiGateTest {
@Test
fun `ShowDetail은 adult guard와 오디오 중단보다 unavailable을 먼저 실행한다`() {
val events = mutableListOf<String>()
runLiveEntryDecisionFlow(
decision = LiveEntryDecision.ShowDetail,
onShowDetail = { events += "unavailable" },
ensureAdultAccess = { allowed ->
events += "adult"
allowed()
},
onAllowedEntry = { events += "entry" }
)
assertEquals(listOf("unavailable"), events)
}
@Test
fun `입장 가능 결정은 adult guard 이후에만 오디오 중단과 입장 처리를 실행한다`() {
val events = mutableListOf<String>()
runLiveEntryDecisionFlow(
decision = LiveEntryDecision.Enter,
onShowDetail = { events += "unavailable" },
ensureAdultAccess = { allowed ->
events += "adult"
allowed()
},
onAllowedEntry = {
events += "audio"
events += "entry"
}
)
assertEquals(listOf("adult", "audio", "entry"), events)
}
@Test
fun `adult guard가 거부되면 오디오 중단과 입장 처리를 실행하지 않는다`() {
val events = mutableListOf<String>()
runLiveEntryDecisionFlow(
decision = LiveEntryDecision.Enter,
onShowDetail = { events += "unavailable" },
ensureAdultAccess = { events += "adult" },
onAllowedEntry = {
events += "audio"
events += "entry"
}
)
assertEquals(listOf("adult"), events)
}
@Test
fun `UI 결과 predicate는 Activity stateSaved 호출 화면 predicate를 모두 결합한다`() {
assertFalse(
canHandleLiveActionUiResult(
isActivityFinishing = true,
isActivityDestroyed = false,
isStateSaved = false,
canHandleResult = { true }
)
)
assertFalse(
canHandleLiveActionUiResult(
isActivityFinishing = false,
isActivityDestroyed = true,
isStateSaved = false,
canHandleResult = { true }
)
)
assertFalse(
canHandleLiveActionUiResult(
isActivityFinishing = false,
isActivityDestroyed = false,
isStateSaved = true,
canHandleResult = { true }
)
)
assertFalse(
canHandleLiveActionUiResult(
isActivityFinishing = false,
isActivityDestroyed = false,
isStateSaved = false,
canHandleResult = { false }
)
)
assertTrue(
canHandleLiveActionUiResult(
isActivityFinishing = false,
isActivityDestroyed = false,
isStateSaved = false,
canHandleResult = { true }
)
)
}
@Test
fun `UI side effect는 lifecycle predicate가 실패하면 실행하지 않는다`() {
var sideEffectCount = 0
val executed = runLiveActionUiSideEffect(canHandleResult = { false }) {
sideEffectCount += 1
}
assertFalse(executed)
assertEquals(0, sideEffectCount)
}
@Test
fun `UI side effect는 lifecycle predicate가 통과하면 한 번 실행한다`() {
var sideEffectCount = 0
val executed = runLiveActionUiSideEffect(canHandleResult = { true }) {
sideEffectCount += 1
}
assertTrue(executed)
assertEquals(1, sideEffectCount)
}
@Test
fun `상세 표시는 lifecycle stateSaved 기존 tag fragment를 모두 검사한다`() {
assertFalse(
canShowLiveRoomDetail(
canHandleUiResult = { false },
existingDetailFragment = null
)
)
assertFalse(
canShowLiveRoomDetail(
canHandleUiResult = { true },
existingDetailFragment = Any()
)
)
assertTrue(
canShowLiveRoomDetail(
canHandleUiResult = { true },
existingDetailFragment = null
)
)
}
}

View File

@@ -0,0 +1,45 @@
package kr.co.vividnext.sodalive.v2.live.action
import org.junit.Assert.assertEquals
import org.junit.Test
class LiveActionsTest {
@Test
fun `취소 또는 실패한 생성 결과는 무시한다`() {
assertEquals(
LiveCreationResult.Ignored,
resolveLiveCreationResult(isSuccessful = false, roomId = 1L, channelName = "channel")
)
}
@Test
fun `channel과 유효한 room ID가 있는 생성 결과는 즉시 입장한다`() {
assertEquals(
LiveCreationResult.Enter(roomId = 1L),
resolveLiveCreationResult(isSuccessful = true, roomId = 1L, channelName = "channel")
)
}
@Test
fun `channel이 없으면 생성 완료만 알린다`() {
assertEquals(
LiveCreationResult.Created,
resolveLiveCreationResult(isSuccessful = true, roomId = 1L, channelName = null)
)
}
@Test
fun `channel은 있지만 room ID가 유효하지 않은 성공 결과도 Home refresh 대상이다`() {
listOf<Long?>(null, 0L, -1L).forEach { roomId ->
assertEquals(
LiveCreationResult.RefreshOnly,
resolveLiveCreationResult(
isSuccessful = true,
roomId = roomId,
channelName = "channel"
)
)
}
}
}