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 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); } } }