test: make ChannelLogoGenerator testable (inject overlay/font paths) for a channel-logo property test #39

Closed
opened 2026-06-27 13:53:36 +02:00 by timothy · 2 comments
Owner

Follow-up to #28, which delivered the XMLTV guide goldens but deliberately skipped the channel-logo image test.

Why the logo test was skipped in #28

ChannelLogoGenerator.GenerateChannelLogo (ErsatzTV.Core/Images/ChannelLogoGenerator.cs) loads:

  • the font from {FileSystemLayout.ResourcesCacheFolder}/Sen.ttf — a global app-data path, and
  • the overlay from a hardcoded relative path Path.Combine("wwwroot", "images", "ersatztv-500.png").

A unit test that exercises the real render would therefore either write into the user's actual ErsatzTV cache folder (unacceptable side effect) or depend on CWD layout — and SkiaSharp output isn't byte-reproducible across platforms/versions anyway. So #28's logo coverage was omitted by design (the #28 issue text pre-authorized "skip if not worth the flakiness").

Proposed (low priority)

Refactor ChannelLogoGenerator to take the overlay + font paths via constructor/DI (or method params) instead of hardcoding them. Update the DI registration + the watermark callers. Then add a robust property test:

  • output bytes decode as a valid PNG,
  • dimensions equal the requested width/height,
  • (optional) the dynamic font-shrink loop behaves for a long channel name.

No byte-exact golden — assert properties, not pixels.

Refs

  • #28 (XMLTV guide goldens — done), #11 (M3U goldens)
Follow-up to **#28**, which delivered the XMLTV guide goldens but **deliberately skipped the channel-logo image test**. ## Why the logo test was skipped in #28 `ChannelLogoGenerator.GenerateChannelLogo` (`ErsatzTV.Core/Images/ChannelLogoGenerator.cs`) loads: - the font from `{FileSystemLayout.ResourcesCacheFolder}/Sen.ttf` — a **global app-data path**, and - the overlay from a hardcoded **relative** path `Path.Combine("wwwroot", "images", "ersatztv-500.png")`. A unit test that exercises the real render would therefore either write into the user's actual ErsatzTV cache folder (unacceptable side effect) or depend on CWD layout — and SkiaSharp output isn't byte-reproducible across platforms/versions anyway. So #28's logo coverage was omitted by design (the #28 issue text pre-authorized "skip if not worth the flakiness"). ## Proposed (low priority) Refactor `ChannelLogoGenerator` to take the overlay + font paths via constructor/DI (or method params) instead of hardcoding them. Update the DI registration + the watermark callers. Then add a robust **property** test: - output bytes decode as a valid PNG, - dimensions equal the requested width/height, - (optional) the dynamic font-shrink loop behaves for a long channel name. No byte-exact golden — assert properties, not pixels. ## Refs - #28 (XMLTV guide goldens — done), #11 (M3U goldens)
Author
Owner

Starting work on #39 in branch feature/39-channel-logo-generator-testable from current origin/main.

Initial read: ChannelLogoGenerator currently hardcodes wwwroot/images/ersatztv-500.png relative to CWD and FileSystemLayout.ResourcesCacheFolder/Sen.ttf, with DI using the default constructor through IChannelLogoGenerator. I’m going to keep runtime defaults intact, add an injectable constructor for explicit overlay/font paths, and add property-style NUnit coverage that decodes the PNG and checks dimensions instead of bytes/pixels.

Starting work on #39 in branch `feature/39-channel-logo-generator-testable` from current `origin/main`. Initial read: `ChannelLogoGenerator` currently hardcodes `wwwroot/images/ersatztv-500.png` relative to CWD and `FileSystemLayout.ResourcesCacheFolder/Sen.ttf`, with DI using the default constructor through `IChannelLogoGenerator`. I’m going to keep runtime defaults intact, add an injectable constructor for explicit overlay/font paths, and add property-style NUnit coverage that decodes the PNG and checks dimensions instead of bytes/pixels.
Author
Owner

Done

What was done: Refactored ChannelLogoGenerator so tests can inject explicit overlay and font paths while the existing DI constructor keeps the current runtime defaults. Added NUnit/Shouldly property-style logo tests that render with test-output assets, decode the generated bytes with SkiaSharp, assert PNG format, and verify requested dimensions across multiple sizes/names including a long-name font-shrink case. Merged via PR #53.

Root cause: n/a for bug-fix purposes. The testability gap existed because the generator directly loaded the overlay from a CWD-relative wwwroot/images/ersatztv-500.png path and the font from the global FileSystemLayout.ResourcesCacheFolder path.

Files changed: ErsatzTV.Core/Images/ChannelLogoGenerator.cs; ErsatzTV.Core.Tests/Images/ChannelLogoGeneratorTests.cs; ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj.

Verification: LSP diagnostics on changed C# files reported no diagnostics. TDD red confirmed the focused test failed before the injectable constructor existed. Focused logo tests passed 3/3. dotnet build ErsatzTV.sln passed with 0 errors and known Scriban.Signed NU1903 warnings. TZ=UTC dotnet test ErsatzTV.sln -v normal passed with visible summary 263/263 tests passed and the same NU1903 warnings. PR CI passed: Build & test (.NET) successful in 4m13s; EF migration integrity successful in 5m14s; PR-only image push job skipped.

Deferred: none.

Follow-up issues: none.

Docs updated: n/a; no operational behavior, API contract, deployment, or schema changes.

Reviewer/adversarial review: Post-implementation adversarial review found no blocking correctness issues. It flagged staging and line-ending hygiene; both were addressed before commit.

## Done **What was done**: Refactored `ChannelLogoGenerator` so tests can inject explicit overlay and font paths while the existing DI constructor keeps the current runtime defaults. Added NUnit/Shouldly property-style logo tests that render with test-output assets, decode the generated bytes with SkiaSharp, assert PNG format, and verify requested dimensions across multiple sizes/names including a long-name font-shrink case. Merged via PR #53. **Root cause**: n/a for bug-fix purposes. The testability gap existed because the generator directly loaded the overlay from a CWD-relative `wwwroot/images/ersatztv-500.png` path and the font from the global `FileSystemLayout.ResourcesCacheFolder` path. **Files changed**: `ErsatzTV.Core/Images/ChannelLogoGenerator.cs`; `ErsatzTV.Core.Tests/Images/ChannelLogoGeneratorTests.cs`; `ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj`. **Verification**: LSP diagnostics on changed C# files reported no diagnostics. TDD red confirmed the focused test failed before the injectable constructor existed. Focused logo tests passed 3/3. `dotnet build ErsatzTV.sln` passed with 0 errors and known `Scriban.Signed` NU1903 warnings. `TZ=UTC dotnet test ErsatzTV.sln -v normal` passed with visible summary 263/263 tests passed and the same NU1903 warnings. PR CI passed: Build & test (.NET) successful in 4m13s; EF migration integrity successful in 5m14s; PR-only image push job skipped. **Deferred**: none. **Follow-up issues**: none. **Docs updated**: n/a; no operational behavior, API contract, deployment, or schema changes. **Reviewer/adversarial review**: Post-implementation adversarial review found no blocking correctness issues. It flagged staging and line-ending hygiene; both were addressed before commit.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#39