Vellum/internal
mithral 608ac5c6d6 Close the Jellyfin test gap
internal/jellyfin is the most client-visible surface in the tree — real Jellyfin
apps parse its JSON — and it had zero test files. Phase 2 had just moved its
DTOs, so this closes the gap before the next refactor.

Coverage: internal/jellyfin 0.0% -> 35.7%, internal/jellyfin/dto -> 68.9%.

The tests, and why each exists:

- Credential forms. X-Emby-Token, X-MediaBrowser-Token, ?api_key= and
  Authorization: MediaBrowser Token="...". The query form matters most: <video>
  and <audio> elements cannot set headers, so a client that can only use
  ?api_key= must still authenticate.
- Grant scoping. A user with no grants sees zero items, and an item they cannot
  read returns a response BYTE-IDENTICAL to one that does not exist. A 403 would
  confirm the library is there and leak its existence. The test asserts the two
  responses are indistinguishable, not merely both non-200.
- The transcode refusal. A client that disables both direct play and direct
  stream is demanding a transcode, and gets ErrorCode NoCompatibleStream rather
  than a TranscodingUrl that would never resolve. The normal path is asserted to
  advertise SupportsTranscoding FALSE, because a client told otherwise waits
  forever for a stream that never starts.
- Streaming with ONLY ?api_key= plus a Range header returns 206 with a correct
  Content-Range and the right bytes, and a token cannot stream a file it cannot
  read.
- AuthenticateByName mints a token that then authenticates, and returns no token
  for bad credentials.
- DTO mapping: exact JSON key set, RunTimeTicks as ms x 10000, id round-trip,
  ImageTags only with a cover, and golden JSON for both the item and the media
  source.

Both halves are proven able to FAIL, because a test that cannot fail is
decoration:
  - flipping SupportsTranscoding to true fails TestMediaSourceNeverTranscodes
  - disabling the grant check in catalog.LoadFor fails
    TestGrantScopingIsIndistinguishableFromAbsent AND TestStreamRefusesUnreadableItem
Both were restored afterwards and the non-test files verified byte-unchanged.

Process note: the agent for this task failed twice (HTTP 400) and left a test
file that did not compile — a bad slices.Delete call and a helper it never
wrote. I fixed those, corrected one wrong expectation (a video item legitimately
carries MediaType; the test's want-list omitted it), and wrote the handler tests
myself since that was the half that never got reached.

gofmt clean, build exit 0, vet exit 0, 25 packages pass, svelte-check 0/0.
2026-10-11 16:10:58 -04:00
..
apitoken Waves 9-10: Jellyfin and Subsonic clients can browse and play 2026-10-11 15:05:43 -04:00
arch Phase 2: split the fat packages along real boundaries 2026-10-11 16:03:10 -04:00
auth Wave 2: correctness and security 2026-10-11 13:31:34 -04:00
catalog Phase 1: one item-access seam, and architecture rules that fail the build 2026-10-11 15:47:18 -04:00
cli Beta blockers: async scans, admin CLI, deploy docs, pagination, password UI 2026-10-10 18:00:35 -04:00
core Wave 1: survive a real library 2026-10-11 13:10:34 -04:00
engine Waves 7-8: playback engine selection, and missing files you can see 2026-10-11 14:36:55 -04:00
home Phase 2: split the fat packages along real boundaries 2026-10-11 16:03:10 -04:00
httpapi Wave 3: search depth, transfer size, and the Home screen's tests 2026-10-11 13:46:18 -04:00
httpx Phase 1: accounts, sessions, per-library grants 2026-10-09 16:31:24 -04:00
jellyfin Close the Jellyfin test gap 2026-10-11 16:10:58 -04:00
jobs Wave 1: survive a real library 2026-10-11 13:10:34 -04:00
library Phase 2: split the fat packages along real boundaries 2026-10-11 16:03:10 -04:00
media Phase 2: split the fat packages along real boundaries 2026-10-11 16:03:10 -04:00
metadata Phase 2: split the fat packages along real boundaries 2026-10-11 16:03:10 -04:00
modules Phase 1: one item-access seam, and architecture rules that fail the build 2026-10-11 15:47:18 -04:00
natsort Phase 6a: directory grouping, audiobooks and music playback 2026-10-09 21:20:47 -04:00
playback Phase 1: one item-access seam, and architecture rules that fail the build 2026-10-11 15:47:18 -04:00
probe Waves 4-6: audio depth, ffprobe facts, and direct-play video 2026-10-11 14:14:32 -04:00
progress Wave 1: survive a real library 2026-10-11 13:10:34 -04:00
providers Phase 2: split the fat packages along real boundaries 2026-10-11 16:03:10 -04:00
scanner Phase 2: split the fat packages along real boundaries 2026-10-11 16:03:10 -04:00
search Wave 3: search depth, transfer size, and the Home screen's tests 2026-10-11 13:46:18 -04:00
stream Waves 4-6: audio depth, ffprobe facts, and direct-play video 2026-10-11 14:14:32 -04:00
subsonic Phase 2: split the fat packages along real boundaries 2026-10-11 16:03:10 -04:00