Skip to content

fix(jellyfin): typed HTTP exception instead of a status code buried in a string - #28

Merged
PonceGL merged 3 commits into
feature/downloadsfrom
chore/p4-jellyfin-typed-exception
Sep 11, 2026
Merged

PonceGL merged 3 commits into
feature/downloadsfrom
chore/p4-jellyfin-typed-exception

Conversation

@PonceGL

@PonceGL PonceGL commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Staged internally first (was chore/p4-jellyfin-typed-exception on PixelPlayerHQ#2819, closed — see that PR's closing comment). Will be proposed upstream again once the downloads feature it's part of is further along.

request() envolvía cualquier fallo en Exception("HTTP $code: ..."), enterrando
el código de estado dentro de un String (GEN-AP-06). Sin él, el clasificador de
errores de F3.3 (docs/downloads) no puede distinguir 401/403/404/416/429/5xx sin
parsear texto.

- JellyfinHttpException(statusCode, retryAfter, message): retryAfter va crudo,
  sin parsear — los dos formatos de Retry-After los decide quien clasifica.
- catch (e: CancellationException) { throw e } antes del catch genérico: el
  catch (e: Exception) existente tragaba la cancelación (AND-CONC-04).
- Cero llamantes en el proyecto parseaban el mensaje anterior; sin regresiones.

Self-review (code-review skill, medium effort) surfaced one finding:
authenticateByName(), a few lines below, has the exact same
CancellationException-swallowing catch (e: Exception) — same bug, same file,
just not the method R19 named. Fixed it too, same one-line pattern, with a
test mirroring the existing deterministic cancel-before-call case. Left its
HTTP-failure paths as plain Exception (not JellyfinHttpException): unlike
request(), nothing downstream needs to distinguish auth failure status codes,
and R19/F3.3 don't ask for it there.

6 tests JVM nuevos (5 + 1 de la autorevisión), en verde. Baseline: 393 tests,
los mismos 5 fallos preexistentes de siempre, ninguno nuevo (check-baseline.sh).
Comments cited local-only planning files and rule IDs (docs/downloads/*.md,
GEN-/AND- rule docs) that don't exist outside this machine and are
meaningless to anyone reviewing the PR. Reworded to keep the same
technical reasoning without the dangling references.
…itory + authenticateByName

code-review on PR #28 found three real issues:

- JellyfinRepository's 8 suspend functions caught generic Exception with
  no CancellationException passthrough, so the cancellation fix in
  JellyfinApiService never reached any real caller — login() is the
  direct case (it calls authenticateByName()), but the same pattern
  wrapped syncPlaylists, syncPlaylistSongs, syncLibrarySongs,
  searchSongs, getLyrics and both syncAllPlaylistsAndSongs() catches.
  Added catch (e: CancellationException) { throw e } to all of them.

- authenticateByName()'s HTTP-failure branch still returned a plain
  Exception with the status code buried in the message, unlike
  request(), which this PR already fixed. Now uses JellyfinHttpException
  for both.

- Both new cancellation tests passed regardless of whether the
  catch (e: CancellationException) { throw e } lines existed, because
  withContext's own pre-flight ensureActive() throws before the
  function body runs when the job is already cancelled — neither test
  actually reached the code they claimed to cover. Added a test that
  throws CancellationException from inside the interceptor instead
  (mid-call, inside the try block), which does exercise the real catch.
  authenticateByName() couldn't get the same test: this module runs
  with unitTests.isReturnDefaultValues = true, so unmocked JSONObject
  returns null before the interceptor is ever reached — documented
  in the test file, not fixed here (needs Robolectric or a JSON
  dependency, out of scope for this PR).

Verified: compiles, full JVM suite green (395 tests, same 5
pre-existing baseline failures as master, none new).
PonceGL added a commit that referenced this pull request Sep 11, 2026
…te cancellation-test comment

code-review on PR #35 found four issues:

- fetchPlayStoreAnnouncement() used .mapCatching { it.toPlayStoreAnnouncementRemoteConfig() },
  which catches Throwable, not just Exception — silently turning a real
  bug or an OutOfMemoryError in that pure mapping into an ordinary
  Result.failure. The pre-refactor code built the config inside a
  catch (e: Exception) branch, so an Error would have propagated as
  intended. Switched to .map (non-catching), matching that behavior.

- The cancellation test's own comment claimed cancelling before the
  call starts 'exercises exactly' the
  catch (e: CancellationException) { throw e } inside fetchRawProperties
  — it doesn't: withContext's pre-flight ensureActive() throws before
  the function body runs, same issue found on PR #28's JellyfinApiService
  tests. Unlike that OkHttp case, HttpURLConnection has no interceptor
  seam to throw an exception mid-call, so there's currently no way to
  write a test that actually reaches this catch. Reworded the comment
  to say so plainly instead of claiming coverage it doesn't have.

- A third finding (HttpURLConnection is fundamentally not cancellable;
  the real fix is migrating to the app's injectable OkHttpClient) was
  already disclosed in this PR's own KDoc as an accepted, deferred
  limitation — left as-is, not re-litigated.

Verified: compiles, full JVM suite green (398 tests, same 5
pre-existing baseline failures as master, none new).
@PonceGL
PonceGL changed the base branch from main to feature/downloads September 11, 2026 15:00
@PonceGL
PonceGL merged commit 71555ab into feature/downloads Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant