feat: M4b inbound ListenBrainz similarity ingest #27

Merged
bvandeusen merged 262 commits from dev into main 2026-04-28 21:22:38 -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.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -18,6 +19,7 @@ import org.junit.jupiter.api.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
@OptIn(ExperimentalCoroutinesApi::class)
class AuthCookieInterceptorTest { class AuthCookieInterceptorTest {
private lateinit var server: MockWebServer private lateinit var server: MockWebServer
private lateinit var authStore: AuthStore private lateinit var authStore: AuthStore
@@ -37,13 +37,14 @@ class LibraryRepositoryTest {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(server.url("/")) .baseUrl(server.url("/"))
.client(OkHttpClient.Builder().build()) .client(OkHttpClient.Builder().build())
.addConverterFactory( .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
Json { ignoreUnknownKeys = true }
.asConverterFactory("application/json".toMediaType()),
)
.build() .build()
} }
private companion object {
private val json = Json { ignoreUnknownKeys = true }
}
@AfterEach @AfterEach
fun teardown() { fun teardown() {
server.shutdown() server.shutdown()
@@ -30,6 +30,13 @@ class LibraryViewModelTest {
assertEquals(LibraryUiState.Loading, vm.uiState.value) 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 @Test
fun `Empty when DAOs emit empty lists`() = runTest { fun `Empty when DAOs emit empty lists`() = runTest {
val repo = mockk<LibraryRepository>() val repo = mockk<LibraryRepository>()
@@ -39,7 +46,6 @@ class LibraryViewModelTest {
val vm = LibraryViewModel(repo) val vm = LibraryViewModel(repo)
vm.uiState.test { vm.uiState.test {
assertEquals(LibraryUiState.Loading, awaitItem())
assertEquals(LibraryUiState.Empty, awaitItem()) assertEquals(LibraryUiState.Empty, awaitItem())
cancelAndConsumeRemainingEvents() cancelAndConsumeRemainingEvents()
} }
@@ -56,7 +62,6 @@ class LibraryViewModelTest {
val vm = LibraryViewModel(repo) val vm = LibraryViewModel(repo)
vm.uiState.test { vm.uiState.test {
assertEquals(LibraryUiState.Loading, awaitItem())
val success = awaitItem() as LibraryUiState.Success val success = awaitItem() as LibraryUiState.Success
assertEquals(1, success.artists.size) assertEquals(1, success.artists.size)
assertEquals(1, success.albums.size) assertEquals(1, success.albums.size)
@@ -76,7 +81,6 @@ class LibraryViewModelTest {
val vm = LibraryViewModel(repo) val vm = LibraryViewModel(repo)
vm.uiState.test { vm.uiState.test {
assertEquals(LibraryUiState.Loading, awaitItem())
val error = awaitItem() as LibraryUiState.Error val error = awaitItem() as LibraryUiState.Error
assertTrue(error.message.contains("DAO blew up")) assertTrue(error.message.contains("DAO blew up"))
cancelAndConsumeRemainingEvents() cancelAndConsumeRemainingEvents()