test(PlaybackState): add state machine transition tests

- Idle → Connecting, Connecting → Playing/Paused/Reconnecting/Idle
- Playing → Paused/Reconnecting/Idle
- Paused → Connecting
- Reconnecting → Connecting/Idle
- Stop from any state returns to Idle

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-11 16:11:14 -04:00
parent f3d28f1a52
commit 0d5992f4a3

View File

@@ -0,0 +1,163 @@
package xyz.cottongin.radio247.service
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import xyz.cottongin.radio247.data.model.Station
class PlaybackStateMachineTest {
private lateinit var stateFlow: MutableStateFlow<PlaybackState>
private val testStation = Station(
id = 1L,
name = "Test Station",
url = "http://example.com/stream"
)
private val testUrls = listOf(
"http://example.com/stream1",
"http://example.com/stream2"
)
@Before
fun setUp() {
stateFlow = MutableStateFlow(PlaybackState.Idle)
}
@Test
fun `idle to connecting on play`() {
val newState = PlaybackState.Connecting(
station = testStation,
urls = testUrls,
currentUrlIndex = 0
)
stateFlow.value = newState
assertTrue(stateFlow.value is PlaybackState.Connecting)
assertEquals(0, (stateFlow.value as PlaybackState.Connecting).currentUrlIndex)
}
@Test
fun `connecting to playing on engine started`() {
stateFlow.value = PlaybackState.Connecting(station = testStation, urls = testUrls)
val connectionStartedAt = System.currentTimeMillis()
stateFlow.value = PlaybackState.Playing(
station = testStation,
sessionStartedAt = connectionStartedAt,
connectionStartedAt = connectionStartedAt
)
assertTrue(stateFlow.value is PlaybackState.Playing)
}
@Test
fun `connecting advances URL index on connection failure`() {
stateFlow.value = PlaybackState.Connecting(
station = testStation, urls = testUrls, currentUrlIndex = 0
)
stateFlow.value = PlaybackState.Connecting(
station = testStation, urls = testUrls, currentUrlIndex = 1
)
assertEquals(1, (stateFlow.value as PlaybackState.Connecting).currentUrlIndex)
}
@Test
fun `connecting to reconnecting when all URLs exhausted and stayConnected`() {
stateFlow.value = PlaybackState.Connecting(
station = testStation, urls = testUrls, currentUrlIndex = 1
)
stateFlow.value = PlaybackState.Reconnecting(
station = testStation, attempt = 1, sessionStartedAt = 0L
)
assertTrue(stateFlow.value is PlaybackState.Reconnecting)
}
@Test
fun `connecting to idle when all URLs exhausted and not stayConnected`() {
stateFlow.value = PlaybackState.Connecting(
station = testStation, urls = testUrls, currentUrlIndex = 1
)
stateFlow.value = PlaybackState.Idle
assertTrue(stateFlow.value is PlaybackState.Idle)
}
@Test
fun `playing to paused on user pause`() {
val now = System.currentTimeMillis()
stateFlow.value = PlaybackState.Playing(
station = testStation, sessionStartedAt = now, connectionStartedAt = now
)
stateFlow.value = PlaybackState.Paused(
station = testStation, sessionStartedAt = now
)
assertTrue(stateFlow.value is PlaybackState.Paused)
}
@Test
fun `playing to reconnecting on connection lost with stayConnected`() {
val now = System.currentTimeMillis()
stateFlow.value = PlaybackState.Playing(
station = testStation, sessionStartedAt = now, connectionStartedAt = now
)
stateFlow.value = PlaybackState.Reconnecting(
station = testStation, sessionStartedAt = now, attempt = 1
)
assertTrue(stateFlow.value is PlaybackState.Reconnecting)
}
@Test
fun `playing to idle on connection lost without stayConnected`() {
val now = System.currentTimeMillis()
stateFlow.value = PlaybackState.Playing(
station = testStation, sessionStartedAt = now, connectionStartedAt = now
)
stateFlow.value = PlaybackState.Idle
assertTrue(stateFlow.value is PlaybackState.Idle)
}
@Test
fun `paused to connecting on resume`() {
val now = System.currentTimeMillis()
stateFlow.value = PlaybackState.Paused(
station = testStation, sessionStartedAt = now
)
stateFlow.value = PlaybackState.Connecting(
station = testStation, urls = testUrls, currentUrlIndex = 0
)
assertTrue(stateFlow.value is PlaybackState.Connecting)
}
@Test
fun `reconnecting to connecting on retry`() {
stateFlow.value = PlaybackState.Reconnecting(
station = testStation, sessionStartedAt = 0L, attempt = 3
)
stateFlow.value = PlaybackState.Connecting(
station = testStation, urls = testUrls, currentUrlIndex = 0
)
assertTrue(stateFlow.value is PlaybackState.Connecting)
}
@Test
fun `reconnecting to idle on user stop`() {
stateFlow.value = PlaybackState.Reconnecting(
station = testStation, sessionStartedAt = 0L, attempt = 2
)
stateFlow.value = PlaybackState.Idle
assertTrue(stateFlow.value is PlaybackState.Idle)
}
@Test
fun `stop from any state returns to idle`() {
val states = listOf(
PlaybackState.Connecting(station = testStation, urls = testUrls),
PlaybackState.Playing(station = testStation, sessionStartedAt = 0L, connectionStartedAt = 0L),
PlaybackState.Paused(station = testStation, sessionStartedAt = 0L),
PlaybackState.Reconnecting(station = testStation, sessionStartedAt = 0L, attempt = 1)
)
for (state in states) {
stateFlow.value = state
stateFlow.value = PlaybackState.Idle
assertTrue("Should be Idle after stop from $state", stateFlow.value is PlaybackState.Idle)
}
}
}