diff --git a/app/src/test/java/xyz/cottongin/radio247/service/PlaybackStateMachineTest.kt b/app/src/test/java/xyz/cottongin/radio247/service/PlaybackStateMachineTest.kt new file mode 100644 index 0000000..c358d4a --- /dev/null +++ b/app/src/test/java/xyz/cottongin/radio247/service/PlaybackStateMachineTest.kt @@ -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 + 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) + } + } +}