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
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
152 lines
6.0 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|