UpdateChannelHandler.Validate never checked incoming graphicsElementIds against
GraphicsElements, so PUT /api/v1/channels/{id} with a non-existent id hit
FK_ChannelGraphicsElement_GraphicsElement_GraphicsElementId at SaveChangesAsync
and surfaced as an unhandled 500. Add GraphicsElementIdsMustExist, following the
existing FFmpegProfileMustExist/WatermarkMustExist/FillerPresetMustExist shape,
so an unknown id now returns 422 for parity with every other FK field on this
full-replace DTO.
GetAllGraphicsElementsForApiHandler and GraphicsElementSeeder.GetBuiltInElementId
keyed builtIn off Path.GetFileName(e.Path) == OnNowNextFileName -- folder-agnostic,
so a user element named exactly on-now-next.yml in any other template folder would
also report builtIn:true. Both now compare against
GraphicsElementDefaults.OnNowNextSeededPath, the full path the seeder actually
writes to.
Follow-up from the #74 whole-branch review (2026-07-22), deferred as
data-safe/not SPA-reachable.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QqCpYFsKgnAnx6jVwrKiV
93 lines
3.0 KiB
C#
93 lines
3.0 KiB
C#
using ErsatzTV.Application.Channels;
|
|
using ErsatzTV.Core;
|
|
using ErsatzTV.Core.Domain;
|
|
using ErsatzTV.Core.Graphics;
|
|
using ErsatzTV.FFmpeg.State;
|
|
using ErsatzTV.Infrastructure.Data;
|
|
using ErsatzTV.Tests.Support;
|
|
using LanguageExt;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using NUnit.Framework;
|
|
using Shouldly;
|
|
|
|
namespace ErsatzTV.Tests.Application.Channels;
|
|
|
|
/// <summary>
|
|
/// #732: the On Now / Next overlay is a default rather than an opt-in, so a channel created after
|
|
/// that decision gets the built-in element without the operator toggling anything.
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class CreateChannelDefaultGraphicsElementTests : ChannelHandlerTestBase
|
|
{
|
|
private CreateChannelHandler MakeHandler() => new(Worker, Db.Factory, SearchTargets, RemoteLogoCacher);
|
|
|
|
private async Task<int> SeedBuiltInElement()
|
|
{
|
|
await using TvContext context = Db.CreateContext();
|
|
var element = new GraphicsElement
|
|
{
|
|
Path = GraphicsElementDefaults.OnNowNextSeededPath,
|
|
Kind = GraphicsElementKind.Text
|
|
};
|
|
|
|
context.GraphicsElements.Add(element);
|
|
await context.SaveChangesAsync();
|
|
return element.Id;
|
|
}
|
|
|
|
private async Task<List<int>> AttachedElementIds(int channelId)
|
|
{
|
|
await using TvContext context = Db.CreateContext();
|
|
Channel reloaded = await context.Channels
|
|
.Include(c => c.ChannelGraphicsElements)
|
|
.SingleAsync(c => c.Id == channelId);
|
|
|
|
return reloaded.ChannelGraphicsElements.Select(x => x.GraphicsElementId).ToList();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Attaches_The_Built_In_Element_To_A_New_Channel()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
int elementId = await SeedBuiltInElement();
|
|
|
|
Either<BaseError, CreateChannelResult> result =
|
|
await MakeHandler().Handle(MakeCreate(), CancellationToken.None);
|
|
|
|
result.IsRight.ShouldBeTrue();
|
|
int channelId = result.RightToSeq().Head().ChannelId;
|
|
|
|
(await AttachedElementIds(channelId)).ShouldBe([elementId]);
|
|
}
|
|
|
|
[Test]
|
|
public async Task Leaves_An_Hls_Direct_Channel_Alone_Because_Nothing_Can_Render_There()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
await SeedBuiltInElement();
|
|
|
|
Either<BaseError, CreateChannelResult> result = await MakeHandler().Handle(
|
|
MakeCreate(streamingMode: StreamingMode.HttpLiveStreamingDirect),
|
|
CancellationToken.None);
|
|
|
|
result.IsRight.ShouldBeTrue();
|
|
int channelId = result.RightToSeq().Head().ChannelId;
|
|
|
|
(await AttachedElementIds(channelId)).ShouldBeEmpty();
|
|
}
|
|
|
|
[Test]
|
|
public async Task Creates_The_Channel_Even_When_The_Built_In_Element_Does_Not_Exist()
|
|
{
|
|
await SeedFFmpegProfile();
|
|
|
|
Either<BaseError, CreateChannelResult> result =
|
|
await MakeHandler().Handle(MakeCreate(), CancellationToken.None);
|
|
|
|
result.IsRight.ShouldBeTrue();
|
|
int channelId = result.RightToSeq().Head().ChannelId;
|
|
|
|
(await AttachedElementIds(channelId)).ShouldBeEmpty();
|
|
}
|
|
}
|