Golden-file tests for M3U / XMLTV / channel-logo output #11

Closed
opened 2026-06-27 00:06:21 +02:00 by timothy · 1 comment
Owner

Risk this addresses

The M3U playlist (ChannelPlaylist.ToM3U()), the XMLTV guide (GetChannelGuideHandler), and channel-logo output are exactly what Jellyfin consumes. Small, well-intentioned changes can silently break that integration — and we have changes queued that touch precisely these surfaces (#1 base-URL fix, #2 REST API, #5 scan behaviour). Right now nothing locks the output shape, so a regression would only show up as Jellyfin misbehaving in prod.

Approach

Add golden-file / approval tests that snapshot known-good output for a handful of representative channels/configs and fail on any unexpected diff:

  • Build baselines from the current output before changing #1, so they capture today's behaviour.
  • Use a fixed clock/seed and normalize volatile fields (timestamps, random ids) so diffs are meaningful.
  • Cover: a simple channel, a channel with a logo, multi-channel playlist, and an XMLTV guide spanning a known window.

Payoff

Turns "did I break the Jellyfin-facing output?" into a CI signal. Makes #1 safe to implement and re-testable. Backlog — ideally landed before #1.

## Risk this addresses The M3U playlist (`ChannelPlaylist.ToM3U()`), the XMLTV guide (`GetChannelGuideHandler`), and channel-logo output are exactly what Jellyfin consumes. Small, well-intentioned changes can silently break that integration — and we have changes queued that touch precisely these surfaces (#1 base-URL fix, #2 REST API, #5 scan behaviour). Right now nothing locks the output shape, so a regression would only show up as Jellyfin misbehaving in prod. ## Approach Add **golden-file / approval tests** that snapshot known-good output for a handful of representative channels/configs and fail on any unexpected diff: - Build baselines from the *current* output **before** changing #1, so they capture today's behaviour. - Use a fixed clock/seed and normalize volatile fields (timestamps, random ids) so diffs are meaningful. - Cover: a simple channel, a channel with a logo, multi-channel playlist, and an XMLTV guide spanning a known window. ## Payoff Turns "did I break the Jellyfin-facing output?" into a CI signal. Makes #1 safe to implement and re-testable. Backlog — ideally landed before #1.
Author
Owner

M3U goldens done — closing this, XMLTV/logo split to #28

Merged via PR #29; full main pipeline green.

What was done — golden-file tests locking ChannelPlaylist.ToM3U() output (the surface #1 touches), in ErsatzTV.Core.Tests/Iptv/ChannelPlaylistGoldenTests.cs + Goldens/*.m3u. Six scenarios capture today's behaviour: simple channel (generated logo), internal-artwork logo, external-URL logo, multi-channel (sorted), Kodi user-agent (KODIPROP lines), base-URL + access-token threading.

Determinism / volatile fields (per the issue): fixed UniqueIds; a [SetUp] guard Assumes ETV_INSTANCE_ID unset (it feeds tvg-id); line endings normalized. Goldens located via [CallerFilePath] (resolve in CI + locally); regenerate via the [Explicit] Regenerate_goldens test or ETV_UPDATE_GOLDENS=1. A missing golden is a hard failure (not a silent skip), so CI can't pass on an un-committed baseline.

Verification: 6/6 pass; proven to have teeth (tampering a golden fails its test); green in real CI (which, given the hard-fail-on-missing, confirms the comparison actually ran).

Why this satisfies the issue's intent: the M3U path is the direct regression net for the paused #1 and is now locked. It's also a pure inputs → string function, so it landed cleanly.

Split to #28: XMLTV guide + channel-logo image golden coverage — the XMLTV handler only assembles pre-built fragments and needs DB + fake-filesystem fixtures; the SkiaSharp logo image isn't byte-deterministic across platforms (assert properties, not bytes). See #28.

Closing #11 since its core goal (lock the M3U / protect #1) is met; #28 carries the remaining surfaces. Reopen/redirect if you'd rather keep #11 open until all three land.

## M3U goldens done — closing this, XMLTV/logo split to #28 Merged via PR #29; full `main` pipeline green. **What was done** — golden-file tests locking `ChannelPlaylist.ToM3U()` output (the surface #1 touches), in `ErsatzTV.Core.Tests/Iptv/ChannelPlaylistGoldenTests.cs` + `Goldens/*.m3u`. Six scenarios capture **today's** behaviour: simple channel (generated logo), internal-artwork logo, external-URL logo, multi-channel (sorted), Kodi user-agent (KODIPROP lines), base-URL + access-token threading. **Determinism / volatile fields** (per the issue): fixed `UniqueId`s; a `[SetUp]` guard `Assume`s `ETV_INSTANCE_ID` unset (it feeds `tvg-id`); line endings normalized. Goldens located via `[CallerFilePath]` (resolve in CI + locally); regenerate via the `[Explicit] Regenerate_goldens` test or `ETV_UPDATE_GOLDENS=1`. A **missing** golden is a hard failure (not a silent skip), so CI can't pass on an un-committed baseline. **Verification**: 6/6 pass; proven to have teeth (tampering a golden fails its test); green in real CI (which, given the hard-fail-on-missing, confirms the comparison actually ran). **Why this satisfies the issue's intent**: the M3U path is the **direct regression net for the paused #1** and is now locked. It's also a pure `inputs → string` function, so it landed cleanly. **Split to #28**: XMLTV guide + channel-logo image golden coverage — the XMLTV handler only assembles pre-built fragments and needs DB + fake-filesystem fixtures; the SkiaSharp logo image isn't byte-deterministic across platforms (assert properties, not bytes). See **#28**. Closing #11 since its core goal (lock the M3U / protect #1) is met; #28 carries the remaining surfaces. Reopen/redirect if you'd rather keep #11 open until all three land.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#11