Files
ersatztv/ErsatzTV.Tests/Infrastructure/DbInitializerChannelBugWatermarkTests.cs
T
timothy 893d4a6398
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 11s
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 10s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 1m6s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 13m25s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 16m1s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 20m26s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 21m19s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
test(67): de-vacuify the template-stamping assertion; cover adopt-then-delete
Whole-branch review found the stamping test was structurally vacuous: a bare
foreach over ChannelTemplates.Where(IsSystem) passes with zero iterations, so
the test would have stayed green if template seeding silently bailed out.
Assert the collection is non-empty first. Proven non-vacuous by a negative
control (forcing SeedChannelTemplates to bail makes exactly this test fail).

Also cover the ACTUAL production sequence -- adopt an existing hand-made row,
then delete it -- which the previous no-resurrect test did not exercise (it
covered seed-then-delete). The marker is written on the adopt path too, so
the deleted row must stay deleted.

docs: note that a deleted preset degrades to no default rather than failing,
and that the default applies to newly created channels, not retroactively.

Refs #67
2026-07-20 21:13:47 +02:00

152 lines
6.0 KiB
C#

using ErsatzTV.Core.Domain;
using ErsatzTV.FFmpeg.State;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Tests.Support;
using NUnit.Framework;
using Shouldly;
namespace ErsatzTV.Tests.Infrastructure;
[TestFixture]
public class DbInitializerChannelBugWatermarkTests
{
private InMemoryTvContext _db = null!;
[SetUp]
public async Task SetUp() => _db = await InMemoryTvContext.CreateAsync();
[TearDown]
public async Task TearDown() => await _db.DisposeAsync();
[Test]
public async Task Initialize_Should_Seed_Channel_Bug_Watermark()
{
await using TvContext context = _db.CreateContext();
await DbInitializer.Initialize(context, CancellationToken.None);
ChannelWatermark watermark = context.ChannelWatermarks.Single(w => w.Name == "Channel Bug");
watermark.Mode.ShouldBe(ChannelWatermarkMode.Permanent);
watermark.ImageSource.ShouldBe(ChannelWatermarkImageSource.ChannelLogo);
watermark.Image.ShouldBeNull();
watermark.Location.ShouldBe(WatermarkLocation.TopLeft);
watermark.Size.ShouldBe(WatermarkSize.Scaled);
watermark.WidthPercent.ShouldBe(5.0);
watermark.HorizontalMarginPercent.ShouldBe(1.0);
watermark.VerticalMarginPercent.ShouldBe(1.0);
watermark.Opacity.ShouldBe(80);
watermark.ZIndex.ShouldBe(0);
watermark.PlaceWithinSourceContent.ShouldBeFalse();
}
// The production instance already has a hand-made "Channel Bug" row with tuned geometry.
// Seeding must adopt it untouched, never overwrite it and never duplicate it.
[Test]
public async Task Initialize_Should_Adopt_Existing_Channel_Bug_Watermark_Untouched()
{
await using TvContext context = _db.CreateContext();
await context.ChannelWatermarks.AddAsync(
new ChannelWatermark
{
Name = "Channel Bug",
Mode = ChannelWatermarkMode.Intermittent,
ImageSource = ChannelWatermarkImageSource.ChannelLogo,
Location = WatermarkLocation.BottomRight,
Size = WatermarkSize.Scaled,
WidthPercent = 12,
HorizontalMarginPercent = 3,
VerticalMarginPercent = 4,
Opacity = 55,
FrequencyMinutes = 10,
DurationSeconds = 20
});
await context.SaveChangesAsync();
await DbInitializer.Initialize(context, CancellationToken.None);
ChannelWatermark watermark = context.ChannelWatermarks.Single(w => w.Name == "Channel Bug");
watermark.Mode.ShouldBe(ChannelWatermarkMode.Intermittent);
watermark.Location.ShouldBe(WatermarkLocation.BottomRight);
watermark.WidthPercent.ShouldBe(12);
watermark.Opacity.ShouldBe(55);
}
[Test]
public async Task Initialize_Should_Be_Idempotent()
{
await using TvContext context = _db.CreateContext();
await DbInitializer.Initialize(context, CancellationToken.None);
await DbInitializer.Initialize(context, CancellationToken.None);
context.ChannelWatermarks.Count(w => w.Name == "Channel Bug").ShouldBe(1);
}
// No IsSystem flag exists on ChannelWatermark, and Initialize runs on every startup, so without
// a seed marker a deliberate delete would be undone forever.
[Test]
public async Task Initialize_Should_Not_Resurrect_A_Deleted_Preset()
{
await using TvContext context = _db.CreateContext();
await DbInitializer.Initialize(context, CancellationToken.None);
ChannelWatermark seeded = context.ChannelWatermarks.Single(w => w.Name == "Channel Bug");
context.ChannelWatermarks.Remove(seeded);
await context.SaveChangesAsync();
await DbInitializer.Initialize(context, CancellationToken.None);
context.ChannelWatermarks.Any(w => w.Name == "Channel Bug").ShouldBeFalse();
}
// The production sequence is ADOPT (an existing hand-made row) and then, possibly, delete —
// not seed-then-delete. The marker is written on the adopt path too, so this must not resurrect.
[Test]
public async Task Initialize_Should_Not_Resurrect_An_Adopted_Preset_After_Deletion()
{
await using TvContext context = _db.CreateContext();
await context.ChannelWatermarks.AddAsync(
new ChannelWatermark
{
Name = "Channel Bug",
Mode = ChannelWatermarkMode.Permanent,
ImageSource = ChannelWatermarkImageSource.ChannelLogo,
Location = WatermarkLocation.BottomRight,
Size = WatermarkSize.Scaled,
WidthPercent = 12,
Opacity = 55
});
await context.SaveChangesAsync();
// First run adopts the existing row and records the marker.
await DbInitializer.Initialize(context, CancellationToken.None);
ChannelWatermark adopted = context.ChannelWatermarks.Single(w => w.Name == "Channel Bug");
context.ChannelWatermarks.Remove(adopted);
await context.SaveChangesAsync();
await DbInitializer.Initialize(context, CancellationToken.None);
context.ChannelWatermarks.Any(w => w.Name == "Channel Bug").ShouldBeFalse();
}
[Test]
public async Task Initialize_Should_Stamp_The_Preset_On_Freshly_Seeded_Templates()
{
await using TvContext context = _db.CreateContext();
await DbInitializer.Initialize(context, CancellationToken.None);
ChannelWatermark watermark = context.ChannelWatermarks.Single(w => w.Name == "Channel Bug");
List<ChannelTemplate> templates = context.ChannelTemplates.Where(t => t.IsSystem).ToList();
// Assert the collection is non-empty FIRST: a bare foreach over zero rows passes vacuously,
// so this test would stay green if template seeding silently bailed out.
templates.Count.ShouldBe(2);
foreach (ChannelTemplate template in templates)
{
template.WatermarkId.ShouldBe(watermark.Id);
}
}
}