diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/TraktWatchedSyncAdapter.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/TraktWatchedSyncAdapter.kt index 755e14bf1..76f5bd0fa 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/TraktWatchedSyncAdapter.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/TraktWatchedSyncAdapter.kt @@ -23,6 +23,36 @@ private const val BASE_URL = "https://api.trakt.tv" private const val WATCHED_PAGE_LIMIT = 250 private const val WATCHED_MAX_PAGES = 1_000 private const val WATCHED_SHOWS_EXTENDED = "progress" +internal const val TRAKT_WATCHED_MAX_RESPONSE_BODY_BYTES = 8 * 1024 * 1024 + +internal fun interface TraktWatchedHttpEngine { + suspend fun get( + url: String, + headers: Map, + maxResponseBodyBytes: Int, + ): RawHttpResponse +} + +internal class TraktWatchedPageClient( + private val engine: TraktWatchedHttpEngine, +) { + suspend fun get(url: String, headers: Map): RawHttpResponse = + engine.get( + url = url, + headers = headers, + maxResponseBodyBytes = TRAKT_WATCHED_MAX_RESPONSE_BODY_BYTES, + ) +} + +private val platformTraktWatchedHttpEngine = TraktWatchedHttpEngine { url, headers, maxResponseBodyBytes -> + httpRequestRaw( + method = "GET", + url = url, + headers = headers, + body = "", + maxResponseBodyBytes = maxResponseBodyBytes, + ) +} object TraktWatchedSyncAdapter : TrackingWatchedProvider { @@ -33,6 +63,7 @@ object TraktWatchedSyncAdapter : TrackingWatchedProvider { encodeDefaults = false explicitNulls = false } + private val pageClient = TraktWatchedPageClient(platformTraktWatchedHttpEngine) // ── pull ──────────────────────────────────────────────────────────── override suspend fun pull( @@ -118,11 +149,9 @@ object TraktWatchedSyncAdapter : TrackingWatchedProvider { val items = mutableListOf() var page = 1 while (page <= WATCHED_MAX_PAGES) { - val response = httpRequestRaw( - method = "GET", + val response = pageClient.get( url = "$BASE_URL/sync/watched/movies?page=$page&limit=$WATCHED_PAGE_LIMIT", headers = headers, - body = "", ) if (response.status !in 200..299) { error("Trakt watched movies request failed: ${response.status}") @@ -144,11 +173,9 @@ object TraktWatchedSyncAdapter : TrackingWatchedProvider { val items = mutableListOf() var page = 1 while (page <= WATCHED_MAX_PAGES) { - val response = httpRequestRaw( - method = "GET", + val response = pageClient.get( url = "$BASE_URL/sync/watched/shows?page=$page&limit=$WATCHED_PAGE_LIMIT&extended=$WATCHED_SHOWS_EXTENDED", headers = headers, - body = "", ) if (response.status !in 200..299) { error("Trakt watched shows request failed: ${response.status}") diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/sync/TraktWatchedSyncAdapterTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/sync/TraktWatchedSyncAdapterTest.kt new file mode 100644 index 000000000..5f6021298 --- /dev/null +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/watching/sync/TraktWatchedSyncAdapterTest.kt @@ -0,0 +1,38 @@ +package com.nuvio.app.features.watching.sync + +import com.nuvio.app.features.addons.DefaultRawHttpResponseMaxBytes +import com.nuvio.app.features.addons.RawHttpResponse +import kotlinx.coroutines.runBlocking +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse + +class TraktWatchedSyncAdapterTest { + @Test + fun `watched history responses larger than generic limit remain complete`() = runBlocking { + val body = "x".repeat(DefaultRawHttpResponseMaxBytes + 1) + var requestedLimit = 0 + val client = TraktWatchedPageClient( + TraktWatchedHttpEngine { _, _, maxResponseBodyBytes -> + requestedLimit = maxResponseBodyBytes + val truncated = body.length > maxResponseBodyBytes + RawHttpResponse( + status = 200, + statusText = "OK", + url = "https://api.trakt.tv/sync/watched/shows", + body = if (truncated) body.take(maxResponseBodyBytes) else body, + headers = emptyMap(), + ) + }, + ) + + val response = client.get( + url = "https://api.trakt.tv/sync/watched/shows", + headers = emptyMap(), + ) + + assertEquals(TRAKT_WATCHED_MAX_RESPONSE_BODY_BYTES, requestedLimit) + assertFalse(response.body.length < body.length) + assertEquals(body, response.body) + } +}