mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Fix: suggestions logic/re-implement logic into worker and address performance issues (#834)
## Description Addresses suggestion logic oversight in recently merged suggestions fix, and improve performance (especially app homepage startup time) - Re-implements the logic that sorts/categorizes tailored suggestions based on the seed items that are queried. This was implemented in the original PR at first, but I overlooked moving that logic - Removes `OneTimeWorkRequestBuilder` and replaces with trigger in `PeriodicWorkRequest` to fire with a 30 seconds delay if cache is empty. ### Related issues Fixes #833 ### Testing Will be testing in: Android TV emulator in Android Studio NVIDIA Shield TV Pro 2019 ## AI or LLM usage I will use AI to help with writing the tests/testing services.
This commit is contained in:
parent
abf5ec8004
commit
8e9a6799f5
4 changed files with 186 additions and 26 deletions
|
|
@ -6,6 +6,7 @@ import androidx.lifecycle.Lifecycle
|
|||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import com.github.damontecres.wholphin.data.CurrentUser
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
|
|
@ -14,6 +15,7 @@ import com.github.damontecres.wholphin.data.model.JellyfinUser
|
|||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -23,10 +25,12 @@ import kotlinx.coroutines.test.resetMain
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class SuggestionsSchedulerServiceTest {
|
||||
|
|
@ -87,4 +91,54 @@ class SuggestionsSchedulerServiceTest {
|
|||
advanceUntilIdle()
|
||||
verify { mockWorkManager.cancelUniqueWork(SuggestionsWorker.WORK_NAME) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun schedules_periodic_work_with_delay_when_cache_empty() =
|
||||
runTest {
|
||||
coEvery { mockCache.isEmpty() } returns true
|
||||
val workRequestSlot = slot<PeriodicWorkRequest>()
|
||||
every {
|
||||
mockWorkManager.enqueueUniquePeriodicWork(
|
||||
SuggestionsWorker.WORK_NAME,
|
||||
any(),
|
||||
capture(workRequestSlot),
|
||||
)
|
||||
} returns mockk()
|
||||
|
||||
createService()
|
||||
currentLiveData.value =
|
||||
CurrentUser(
|
||||
user = JellyfinUser(id = UUID.randomUUID(), name = "User", serverId = UUID.randomUUID(), accessToken = "token"),
|
||||
server = JellyfinServer(id = UUID.randomUUID(), name = "Server", url = "http://localhost", version = null),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
verify { mockWorkManager.enqueueUniquePeriodicWork(SuggestionsWorker.WORK_NAME, any(), any()) }
|
||||
assertEquals(30000L, workRequestSlot.captured.workSpec.initialDelay)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun schedules_periodic_work_without_delay_when_cache_not_empty() =
|
||||
runTest {
|
||||
coEvery { mockCache.isEmpty() } returns false
|
||||
val workRequestSlot = slot<PeriodicWorkRequest>()
|
||||
every {
|
||||
mockWorkManager.enqueueUniquePeriodicWork(
|
||||
SuggestionsWorker.WORK_NAME,
|
||||
any(),
|
||||
capture(workRequestSlot),
|
||||
)
|
||||
} returns mockk()
|
||||
|
||||
createService()
|
||||
currentLiveData.value =
|
||||
CurrentUser(
|
||||
user = JellyfinUser(id = UUID.randomUUID(), name = "User", serverId = UUID.randomUUID(), accessToken = "token"),
|
||||
server = JellyfinServer(id = UUID.randomUUID(), name = "Server", url = "http://localhost", version = null),
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
verify { mockWorkManager.enqueueUniquePeriodicWork(SuggestionsWorker.WORK_NAME, any(), any()) }
|
||||
assertEquals(0L, workRequestSlot.captured.workSpec.initialDelay)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,85 @@ class SuggestionsWorkerTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetches_contextual_suggestions_when_genres_available() =
|
||||
runTest {
|
||||
val viewId = UUID.randomUUID()
|
||||
val view =
|
||||
mockk<BaseItemDto>(relaxed = true) {
|
||||
every { id } returns viewId
|
||||
every { this@mockk.collectionType } returns CollectionType.MOVIES
|
||||
}
|
||||
every { mockPreferences.data } returns flowOf(mockPrefs())
|
||||
coEvery { mockUserViewsApi.getUserViews(userId = testUserId) } returns mockQueryResult(listOf(view))
|
||||
mockkObject(GetItemsRequestHandler)
|
||||
|
||||
val genreId = UUID.randomUUID()
|
||||
val historyItem =
|
||||
mockk<BaseItemDto>(relaxed = true) {
|
||||
every { id } returns UUID.randomUUID()
|
||||
every { genreItems } returns listOf(mockk { every { id } returns genreId })
|
||||
}
|
||||
val contextualItem = mockk<BaseItemDto>(relaxed = true) { every { id } returns UUID.randomUUID() }
|
||||
val randomItem = mockk<BaseItemDto>(relaxed = true) { every { id } returns UUID.randomUUID() }
|
||||
val freshItem = mockk<BaseItemDto>(relaxed = true) { every { id } returns UUID.randomUUID() }
|
||||
|
||||
var callCount = 0
|
||||
coEvery { GetItemsRequestHandler.execute(mockApi, any()) } answers {
|
||||
callCount++
|
||||
when (callCount) {
|
||||
1 -> mockQueryResult(listOf(historyItem))
|
||||
2 -> mockQueryResult(listOf(contextualItem))
|
||||
3 -> mockQueryResult(listOf(randomItem))
|
||||
4 -> mockQueryResult(listOf(freshItem))
|
||||
else -> mockQueryResult(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
val result = createWorker().doWork()
|
||||
|
||||
assertEquals(ListenableWorker.Result.success(), result)
|
||||
coVerify { mockCache.put(testUserId, viewId, BaseItemKind.MOVIE, any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun skips_contextual_suggestions_when_no_genres_available() =
|
||||
runTest {
|
||||
val viewId = UUID.randomUUID()
|
||||
val view =
|
||||
mockk<BaseItemDto>(relaxed = true) {
|
||||
every { id } returns viewId
|
||||
every { this@mockk.collectionType } returns CollectionType.MOVIES
|
||||
}
|
||||
every { mockPreferences.data } returns flowOf(mockPrefs())
|
||||
coEvery { mockUserViewsApi.getUserViews(userId = testUserId) } returns mockQueryResult(listOf(view))
|
||||
mockkObject(GetItemsRequestHandler)
|
||||
|
||||
val historyItem =
|
||||
mockk<BaseItemDto>(relaxed = true) {
|
||||
every { id } returns UUID.randomUUID()
|
||||
every { genreItems } returns emptyList()
|
||||
}
|
||||
val randomItem = mockk<BaseItemDto>(relaxed = true) { every { id } returns UUID.randomUUID() }
|
||||
val freshItem = mockk<BaseItemDto>(relaxed = true) { every { id } returns UUID.randomUUID() }
|
||||
|
||||
var callCount = 0
|
||||
coEvery { GetItemsRequestHandler.execute(mockApi, any()) } answers {
|
||||
callCount++
|
||||
when (callCount) {
|
||||
1 -> mockQueryResult(listOf(historyItem))
|
||||
2 -> mockQueryResult(listOf(randomItem))
|
||||
3 -> mockQueryResult(listOf(freshItem))
|
||||
else -> mockQueryResult(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
val result = createWorker().doWork()
|
||||
|
||||
assertEquals(ListenableWorker.Result.success(), result)
|
||||
coVerify { mockCache.put(testUserId, viewId, BaseItemKind.MOVIE, any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun returns_retry_on_network_error() =
|
||||
runTest {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue