feat: M3 weighted shuffle v1 — real /api/radio with scoring #21

Merged
bvandeusen merged 262 commits from dev into main 2026-04-27 11:00:29 -04:00
3 changed files with 14 additions and 7 deletions
Showing only changes of commit b03d4a86e7 - Show all commits
@@ -5,6 +5,7 @@ import com.fabledsword.minstrel.cache.db.dao.AuthSessionDao
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -18,6 +19,7 @@ import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
@OptIn(ExperimentalCoroutinesApi::class)
class AuthCookieInterceptorTest {
private lateinit var server: MockWebServer
private lateinit var authStore: AuthStore
@@ -37,13 +37,14 @@ class LibraryRepositoryTest {
Retrofit.Builder()
.baseUrl(server.url("/"))
.client(OkHttpClient.Builder().build())
.addConverterFactory(
Json { ignoreUnknownKeys = true }
.asConverterFactory("application/json".toMediaType()),
)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
}
private companion object {
private val json = Json { ignoreUnknownKeys = true }
}
@AfterEach
fun teardown() {
server.shutdown()
@@ -30,6 +30,13 @@ class LibraryViewModelTest {
assertEquals(LibraryUiState.Loading, vm.uiState.value)
}
// Note: tests below collect the resolved state directly. With
// UnconfinedTestDispatcher, stateIn's upstream Flow runs synchronously
// when the first subscriber attaches — so the `Loading` initialValue
// is replaced by the upstream emission before .test{} sees it. The
// observable behavior we care about is the resolved state, not the
// intermediate Loading.
@Test
fun `Empty when DAOs emit empty lists`() = runTest {
val repo = mockk<LibraryRepository>()
@@ -39,7 +46,6 @@ class LibraryViewModelTest {
val vm = LibraryViewModel(repo)
vm.uiState.test {
assertEquals(LibraryUiState.Loading, awaitItem())
assertEquals(LibraryUiState.Empty, awaitItem())
cancelAndConsumeRemainingEvents()
}
@@ -56,7 +62,6 @@ class LibraryViewModelTest {
val vm = LibraryViewModel(repo)
vm.uiState.test {
assertEquals(LibraryUiState.Loading, awaitItem())
val success = awaitItem() as LibraryUiState.Success
assertEquals(1, success.artists.size)
assertEquals(1, success.albums.size)
@@ -76,7 +81,6 @@ class LibraryViewModelTest {
val vm = LibraryViewModel(repo)
vm.uiState.test {
assertEquals(LibraryUiState.Loading, awaitItem())
val error = awaitItem() as LibraryUiState.Error
assertTrue(error.message.contains("DAO blew up"))
cancelAndConsumeRemainingEvents()