package com.suno.android.media.player.pool import android.os.Looper import androidx.media3.common.Player import com.suno.android.common_core_utils.Id import com.suno.android.common_core_utils.SunoLogger import com.suno.android.media.player.PlayerFactory import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkAll import io.mockk.verify import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertSame import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test class PlayerPoolTest { private val loggerFactory = mockk(relaxed = true) private val playerFactory = mockk() private val playerSlotFactory = mockk { every { create(any()) } answers { PlayerSlot( loggerFactory = loggerFactory, poolIndex = firstArg(), ) } } private val subject = PlayerPoolImpl( loggerFactory = loggerFactory, playerSlotFactory = playerSlotFactory, playerFactory = playerFactory, ) @Before fun setUp() { mockkStatic(Looper::class) val mainLooper = mockk() every { Looper.getMainLooper() } returns mainLooper every { Looper.myLooper() } returns mainLooper } @After fun tearDown() { unmockkAll() } @Test fun `given empty pool when assign player then creates new player and returns it`() { val mediaId = createMediaId("test1") val mockPlayer = mockk(relaxed = true) every { playerFactory.create() } returns mockPlayer val result = subject.assignPlayer(mediaId = mediaId, index = 0) assertTrue(result.isSuccess) val player = result.getOrThrow() assertSame(mockPlayer, player) verify { playerFactory.create() } } @Test fun `given pool with available slots when assign player then uses unoccupied slot`() { val mediaId1 = createMediaId("test1") val mediaId2 = createMediaId("test2") val mockPlayer1 = mockk(relaxed = true) val mockPlayer2 = mockk(relaxed = true) every { playerFactory.create() } returnsMany listOf(mockPlayer1, mockPlayer2) val result1 = subject.assignPlayer(mediaId = mediaId1, index = 0) val result2 = subject.assignPlayer(mediaId = mediaId2, index = 1) assertTrue(result1.isSuccess) assertTrue(result2.isSuccess) verify(exactly = 2) { playerFactory.create() } } @Test fun `given full pool when scrolling up and assign player then evicts player farthest from current position`() { val mediaIds = (0..4).map { createMediaId("scroll_up_$it") } val mockPlayers = (0..3).map { mockk(relaxed = true) } every { playerFactory.create() } returnsMany mockPlayers listOf(10, 11, 12, 13).forEachIndexed { idx, mediaIndex -> val result = subject.assignPlayer( mediaId = mediaIds[idx], index = mediaIndex, ) assertTrue(result.isSuccess) } subject.updateCurrentlyPlayingIndex(index = 11) assertNotNull(subject.assignments[mediaIds[3]]) val result = subject.assignPlayer(mediaId = mediaIds[4], index = 9) assertTrue(result.isSuccess) assertNotNull(subject.assignments[mediaIds[4]]) assertNull(subject.assignments[mediaIds[3]]) } @Test fun `given full pool when scrolling down and assign player then evicts player farthest from current position`() { val mediaIds = (0..4).map { createMediaId("scroll_down_$it") } val mockPlayers = (0..3).map { mockk(relaxed = true) } every { playerFactory.create() } returnsMany mockPlayers listOf(10, 11, 12, 13).forEachIndexed { idx, mediaIndex -> val result = subject.assignPlayer( mediaId = mediaIds[idx], index = mediaIndex, ) assertTrue(result.isSuccess) } subject.updateCurrentlyPlayingIndex(index = 11) assertNotNull(subject.assignments[mediaIds[0]]) val result = subject.assignPlayer(mediaId = mediaIds[4], index = 13) assertTrue(result.isSuccess) assertNotNull(subject.assignments[mediaIds[4]]) assertNull(subject.assignments[mediaIds[0]]) } @Test fun `given existing assignment when assign same media ID then returns existing player`() { val mediaId = createMediaId("test1") val mockPlayer = mockk(relaxed = true) every { playerFactory.create() } returns mockPlayer val firstResult = subject.assignPlayer(mediaId = mediaId, index = 0) val secondResult = subject.assignPlayer(mediaId = mediaId, index = 0) assertTrue(firstResult.isSuccess) assertTrue(secondResult.isSuccess) assertSame(firstResult.getOrThrow(), secondResult.getOrThrow()) verify(exactly = 1) { playerFactory.create() } } @Test fun `given player factory fails when assign player then returns failure result`() { val mediaId = createMediaId("test1") val exception = RuntimeException("Player creation failed") every { playerFactory.create() } throws exception val result = subject.assignPlayer(mediaId = mediaId, index = 0) assertTrue(result.isFailure) assertEquals(exception, result.exceptionOrNull()) } @Test fun `given no assignment when unassign player then does nothing`() { val mediaId = createMediaId("nonexistent") subject.unassignPlayer(mediaId) } @Test fun `given pool with assignments when release all players then clears all assignments and releases slots`() { val mediaIds = (0..2).map { createMediaId("test$it") } val mockPlayers = (0..2).map { mockk(relaxed = true) } every { playerFactory.create() } returnsMany mockPlayers mediaIds.forEachIndexed { index, mediaId -> subject.assignPlayer(mediaId = mediaId, index = index) } subject.releaseAllPlayers() assertTrue(subject.assignments.isEmpty()) mockPlayers.forEach { player -> verify { player.release() } } } private fun createMediaId( value: String, ): Id = Id(value) }