Golden tests for XMLTV guide + channel-logo output (follow-up to #11) #28

Closed
opened 2026-06-27 10:16:28 +02:00 by timothy · 2 comments
Owner

Follow-up to #11, which landed golden-file tests for the M3U output (ChannelPlaylist.ToM3U) — the direct regression net for the paused #1. This issue tracks the remaining two surfaces, split out because each needs more setup than the pure-function M3U path:

XMLTV guide

GetChannelGuideHandler doesn't generate the guide itself — it reads pre-built channels.xml + per-channel fragments from FileSystemLayout.ChannelGuideCacheFolder, substitutes {RequestBase}/{AccessTokenUri}, strips etv: tags, and assembles a ChannelGuide. A golden test needs:

  • a fake filesystem (Testably.Abstractions — already a test dep) seeded with representative fragment files,
  • an in-memory/SQLite TvContext with a couple of channels (incl. one ShowInEpg = false to exercise hiding),
  • normalization of the volatile ?v={mtime} cache-buster.
    Snapshot the assembled guide for: a guide with one visible + one hidden channel, with and without an access token. (Consider also golden-testing the fragment generation itself if there's a pure-ish entry point — find where the fragments are written.)

Channel-logo image

ChannelLogoGenerator renders via SkiaSharp. A byte-exact golden is non-deterministic across platforms / SkiaSharp versions, so prefer asserting properties (valid PNG/JPEG, expected dimensions, maybe a perceptual/structural hash with tolerance) rather than exact bytes — or skip if not worth the flakiness.

Why split

The M3U path is a pure inputs → string function (deterministic, no fixtures) and is the surface #1 touches, so it landed cleanly in #11. These two carry fixture/non-determinism cost that's better isolated. Backlog.

Follow-up to **#11**, which landed golden-file tests for the **M3U** output (`ChannelPlaylist.ToM3U`) — the direct regression net for the paused #1. This issue tracks the remaining two surfaces, split out because each needs more setup than the pure-function M3U path: ## XMLTV guide `GetChannelGuideHandler` doesn't generate the guide itself — it reads pre-built `channels.xml` + per-channel fragments from `FileSystemLayout.ChannelGuideCacheFolder`, substitutes `{RequestBase}`/`{AccessTokenUri}`, strips `etv:` tags, and assembles a `ChannelGuide`. A golden test needs: - a fake filesystem (`Testably.Abstractions` — already a test dep) seeded with representative fragment files, - an in-memory/SQLite `TvContext` with a couple of channels (incl. one `ShowInEpg = false` to exercise hiding), - normalization of the volatile `?v={mtime}` cache-buster. Snapshot the assembled guide for: a guide with one visible + one hidden channel, with and without an access token. (Consider also golden-testing the *fragment generation* itself if there's a pure-ish entry point — find where the fragments are written.) ## Channel-logo image `ChannelLogoGenerator` renders via SkiaSharp. A byte-exact golden is **non-deterministic across platforms / SkiaSharp versions**, so prefer asserting *properties* (valid PNG/JPEG, expected dimensions, maybe a perceptual/structural hash with tolerance) rather than exact bytes — or skip if not worth the flakiness. ## Why split The M3U path is a pure `inputs → string` function (deterministic, no fixtures) and is the surface #1 touches, so it landed cleanly in #11. These two carry fixture/non-determinism cost that's better isolated. Backlog.
Author
Owner

Starting on branch test/28-xmltv-logo-goldens. Plan, following the #11 harness ([CallerFilePath] Goldens dir, ETV_UPDATE_GOLDENS regen, missing-golden = hard fail):

  • XMLTV guide goldens — drive GetChannelGuideHandler with a fake filesystem (Testably) seeded with representative channels.xml + per-channel fragments, and a SQLite TvContext with one visible + one ShowInEpg=false channel. Normalize the volatile ?v={mtime} cache-buster. Snapshot with and without an access token.
  • Channel-logo image — assert properties (valid PNG, expected dimensions) rather than byte-exact, since SkiaSharp output is platform/version-dependent (per this issue). Will skip if it proves flaky rather than ship a fragile golden.

Scoping the handler + existing test infra now; will report the concrete shape before writing.

Starting on branch `test/28-xmltv-logo-goldens`. Plan, following the #11 harness (`[CallerFilePath]` Goldens dir, `ETV_UPDATE_GOLDENS` regen, missing-golden = hard fail): - **XMLTV guide goldens** — drive `GetChannelGuideHandler` with a fake filesystem (Testably) seeded with representative `channels.xml` + per-channel fragments, and a SQLite `TvContext` with one visible + one `ShowInEpg=false` channel. Normalize the volatile `?v={mtime}` cache-buster. Snapshot with and without an access token. - **Channel-logo image** — assert *properties* (valid PNG, expected dimensions) rather than byte-exact, since SkiaSharp output is platform/version-dependent (per this issue). Will skip if it proves flaky rather than ship a fragile golden. Scoping the handler + existing test infra now; will report the concrete shape before writing.
Author
Owner

Done (XMLTV guide half) — merged to main via #40

What was done: Added ChannelGuideGoldenTests — golden-file tests driving the real GetChannelGuideHandler against a Testably fake filesystem (seeded channels.xml + per-channel fragments) and a SQLite-backed IDbContextFactory<TvContext>, snapshotting ChannelGuide.ToXml(). Same [CallerFilePath] Goldens + ETV_UPDATE_GOLDENS harness as the M3U goldens (#11). Covers {RequestBase} host substitution (the XMLTV counterpart to the #1 net), {AccessTokenUri} (with/without token), etv-tag stripping (paired + self-closing), hidden-channel exclusion, decimal fragment ordering (channels 2 vs 10), and external-URL passthrough. The volatile ?v={mtime} cache-buster is normalized.

Files changed: ErsatzTV.Core.Tests/Iptv/ChannelGuideGoldenTests.cs (new) + 3 goldens under Iptv/Goldens/guide-*.xml; ErsatzTV.Core.Tests.csproj gains an ErsatzTV.Application reference (the handler lives there).

Adversarial review: ran a harsh self-review over the diff; folded in the fixes (removed a dead/leaking TvContext.IsSqlite static, made decimal ordering non-vacuous, canonicalized a trailing newline for .editorconfig, exercised the channels.xml skip branch, switched seeding to EnsureCreatedAsync — suite back to ~2s).

Deferred: the channel-logo image test — ChannelLogoGenerator reads its font from a global ResourcesCacheFolder path and its overlay from a hardcoded relative path, and SkiaSharp isn't byte-reproducible, so a clean test needs a small DI refactor. Split out to #39. (This issue's text pre-authorized skipping it.)

Follow-up issues: #39 (logo-generator DI refactor → property test).

Docs updated: n/a (test-only; the goldens are self-documenting).

Verification: PR #40 — Build & test (.NET) ✓ and EF migration integrity (SQLite + MySql) ✓. Full ErsatzTV.Core.Tests: 484 passed, 0 failed (TZ=UTC).

Closing — XMLTV guide goldens delivered; logo image coverage tracked in #39.

## Done (XMLTV guide half) — merged to `main` via #40 **What was done**: Added `ChannelGuideGoldenTests` — golden-file tests driving the real `GetChannelGuideHandler` against a Testably fake filesystem (seeded `channels.xml` + per-channel fragments) and a SQLite-backed `IDbContextFactory<TvContext>`, snapshotting `ChannelGuide.ToXml()`. Same `[CallerFilePath]` Goldens + `ETV_UPDATE_GOLDENS` harness as the M3U goldens (#11). Covers `{RequestBase}` host substitution (the XMLTV counterpart to the #1 net), `{AccessTokenUri}` (with/without token), etv-tag stripping (paired + self-closing), hidden-channel exclusion, decimal fragment ordering (channels 2 vs 10), and external-URL passthrough. The volatile `?v={mtime}` cache-buster is normalized. **Files changed**: `ErsatzTV.Core.Tests/Iptv/ChannelGuideGoldenTests.cs` (new) + 3 goldens under `Iptv/Goldens/guide-*.xml`; `ErsatzTV.Core.Tests.csproj` gains an `ErsatzTV.Application` reference (the handler lives there). **Adversarial review**: ran a harsh self-review over the diff; folded in the fixes (removed a dead/leaking `TvContext.IsSqlite` static, made decimal ordering non-vacuous, canonicalized a trailing newline for `.editorconfig`, exercised the `channels.xml` skip branch, switched seeding to `EnsureCreatedAsync` — suite back to ~2s). **Deferred**: the **channel-logo image** test — `ChannelLogoGenerator` reads its font from a global `ResourcesCacheFolder` path and its overlay from a hardcoded relative path, and SkiaSharp isn't byte-reproducible, so a clean test needs a small DI refactor. Split out to **#39**. (This issue's text pre-authorized skipping it.) **Follow-up issues**: #39 (logo-generator DI refactor → property test). **Docs updated**: n/a (test-only; the goldens are self-documenting). **Verification**: PR #40 — `Build & test (.NET)` ✓ and `EF migration integrity (SQLite + MySql)` ✓. Full `ErsatzTV.Core.Tests`: 484 passed, 0 failed (`TZ=UTC`). Closing — XMLTV guide goldens delivered; logo image coverage tracked in #39.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#28